Skip to content
Open
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
51 changes: 49 additions & 2 deletions crates/socket-patch-cli/src/commands/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,21 @@ async fn run_check(args: &ApplyArgs, manifest_path: &Path) -> i32 {
drifts.push((id, "go_redirect_drift".to_string(), d.to_string()));
}
}
// The redirects can be intact while the project no longer
// builds: a committed vendor/modules.txt or the go.mod
// requirements out of step with them (#343, #618).
for issue in socket_patch_core::vendor::go_consumer_sync::audit(
&args.common.cwd,
&HashMap::new(),
)
.await
{
drifts.push((
issue.module().to_string(),
issue.code().to_string(),
issue.to_string(),
));
}
}
}

Expand Down Expand Up @@ -422,7 +437,14 @@ async fn run_check(args: &ApplyArgs, manifest_path: &Path) -> i32 {
for (_, _, detail) in &drifts {
eprintln!(" {detail}");
}
eprintln!("{}", check_remedy(&args.common));
// A go consumer-sync drift names its own go command; apply
// cannot regenerate vendor/modules.txt or go.sum.
if drifts.iter().any(|(_, code, _)| {
code != socket_patch_core::vendor::go_consumer_sync::VENDOR_MODULES_TXT_CODE
&& code != socket_patch_core::vendor::go_consumer_sync::REQUIREMENTS_CODE
}) {
eprintln!("{}", check_remedy(&args.common));
}
}
1
}
Expand Down Expand Up @@ -1991,6 +2013,7 @@ async fn apply_patches_inner(
}
}
let mut fallback_skips: Vec<FallbackHomeSkip> = Vec::new();
let mut go_pristine_mods: HashMap<String, PathBuf> = HashMap::new();

// Multi-copy aware: npm nests genuine duplicates of one `name@version`
// (nested dupes, diamonds, `file:` dups), so the resolver returns EVERY
Expand Down Expand Up @@ -2403,7 +2426,16 @@ async fn apply_patches_inner(
match try_local_go_apply(purl, pkg_path, patch, &sources, &args.common, policy)
.await
{
Some(r) => r,
Some(r) => {
// The unpatched go.mod, so the consumer-sync
// audit below can tell requirements the patch
// added from ones upstream always had (#618).
if let Some((module, _)) = parse_golang_purl(purl) {
go_pristine_mods
.insert(module.into_owned(), pkg_path.join("go.mod"));
}
r
}
None => {
apply_package_patch(
purl,
Expand Down Expand Up @@ -2482,6 +2514,21 @@ async fn apply_patches_inner(

// The human summary is printed by `run`, after the per-package list.

// A redirect leaves the project building only if its committed
// `vendor/modules.txt` and its go.mod requirements still agree with the
// `replace` directives (#343, #618): name the regeneration step.
if !args.common.dry_run && eco_in_local_scope(&args.common, Ecosystem::Golang) {
run_warnings.extend(
socket_patch_core::vendor::go_consumer_sync::audit_warnings(
&args.common.cwd,
&go_pristine_mods,
)
.await
.into_iter()
.map(|(code, detail)| RunWarning { code, detail }),
);
}

// Note: `apply` deliberately does NOT garbage-collect unused blobs in
// `.socket/`. GC is the responsibility of `socket-patch repair` /
// `gc` / `scan --prune`. Keeping apply read-only against `.socket/`
Expand Down
13 changes: 13 additions & 0 deletions crates/socket-patch-cli/src/commands/rollback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2659,6 +2659,19 @@ pub(crate) async fn rollback_patches_inner(
results.push(result);
}

// Dropping a `replace` leaves a committed vendor/modules.txt recording
// it, which breaks every vendored build until it is regenerated (#343).
if !common.dry_run
&& results
.iter()
.any(|r| r.success && is_local_go(&r.package_key, common))
{
let none = std::collections::HashMap::new();
warnings.extend(
socket_patch_core::vendor::go_consumer_sync::audit_warnings(&common.cwd, &none).await,
);
}

superseded_left.sort();
superseded_left.dedup();
Ok(RollbackOutcome {
Expand Down
18 changes: 18 additions & 0 deletions crates/socket-patch-cli/src/commands/scan/hosted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1615,6 +1615,24 @@ pub(crate) async fn run_redirect_selected(
engine_warnings.extend(done.rush_warnings.iter().cloned());
engine_warnings.extend(done.pnpm_warnings.iter().cloned());
engine_warnings.extend(done.npm_warnings.iter().cloned());
// A hosted Go `replace` breaks a committed vendor/ directory until
// `go mod vendor` re-records it in vendor/modules.txt (#343).
if !common.dry_run
&& done
.rewritten
.iter()
.any(|f| f == "go.mod" || f.ends_with("/go.mod"))
{
for (code, detail) in socket_patch_core::vendor::go_consumer_sync::audit_warnings(
&common.cwd,
&std::collections::HashMap::new(),
)
.await
{
engine_warnings
.push(socket_patch_core::patch::redirect::RewriteWarning { code, detail });
}
}
let mut warnings: Vec<serde_json::Value> =
socket_patch_core::hosted::render::rewrite_warnings_json(&engine_warnings);
warnings.extend(gem_stale.warnings.iter().cloned());
Expand Down
37 changes: 37 additions & 0 deletions crates/socket-patch-cli/src/commands/vendor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,7 @@ async fn run_check(args: &VendorArgs) -> i32 {
} else {
None
};
let go_sync_issues = tokio::sync::OnceCell::new();
for (key, entry) in entries {
let record = entry.record.as_ref().or_else(|| manifest.patches.get(key));
let mut failure = match record {
Expand Down Expand Up @@ -1240,6 +1241,26 @@ async fn run_check(args: &VendorArgs) -> i32 {
failure = Some(unwired_check_failure(discovery, root, key, entry).await);
}
}
// The committed copy and its `replace` can be intact while the
// project no longer builds: vendor/modules.txt or the go.mod
// requirements out of step with the `replace` (#343, #618).
if failure.is_none() && entry.ecosystem == "golang" {
if let Some((module, _)) = socket_patch_core::utils::purl::parse_golang_purl(key) {
let issues: Vec<String> = go_sync_issues
.get_or_init(|| async {
socket_patch_core::vendor::go_consumer_sync::audit(root, &HashMap::new())
.await
})
.await
.iter()
.filter(|issue| issue.module() == module)
.map(ToString::to_string)
.collect();
if !issues.is_empty() {
failure = Some(issues.join("; "));
}
}
}
if vendor::jvm::apply::upstream_unverified(entry) {
env.warnings.push(RunWarning {code: "vendor_jvm_upstream_unverified".into(), detail: format!("{key}: upstream metadata was accepted offline; run vendor online to verify registry checksums")});
}
Expand Down Expand Up @@ -3750,6 +3771,22 @@ pub(crate) async fn vendor_records_reusing(
}
}

// A vendored Go module builds only if the committed vendor/modules.txt
// and the go.mod requirements agree with its `replace` (#343, #618).
if !common.dry_run && records.keys().any(|p| p.starts_with("pkg:golang/")) {
let warnings = socket_patch_core::vendor::go_consumer_sync::audit_warnings(
&common.cwd,
&HashMap::new(),
)
.await;
for (code, detail) in warnings {
if !common.json && !common.silent {
eprintln!("Warning: {detail}");
}
env.warnings.push(RunWarning { code, detail });
}
}

// A rolled-back eject's summary would describe a vendoring that was
// undone: the eject prints it itself once it knows the outcome (#1005).
match eject {
Expand Down
Loading
Loading