Skip to content

Commit 67abc02

Browse files
mikolalysenkoclaude
andcommitted
Fix Go check, drop and vendor findings from code review
- apply --check: skip the native vendor check for a module whose modules.txt drift the audit already reports, so one cause gives one drift (matches go_sync_warnings). - drop_replace_entry: plan the go.sum restore, remove the directive, and only then write go.sum, so a failed go.mod/go.work edit leaves both unchanged. - Native vendor: treat a file with two //go:build lines or an unparseable expression as omitted, as imports.ShouldBuild does. - reconcile: report a failed drop of a version-less directive under a PURL built from its copy path, not the bare module path. - Only probe selection when replace_refusal did not already refuse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 4eec336 commit 67abc02

5 files changed

Lines changed: 113 additions & 46 deletions

File tree

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

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -407,38 +407,52 @@ async fn run_check(args: &ApplyArgs, manifest_path: &Path) -> i32 {
407407
drifts.push((id, "go_redirect_drift".to_string(), d.to_string()));
408408
}
409409
}
410-
// The native vendor tree a default build compiles must carry
411-
// every in-scope Go patch, vendor-owned ones included. Its
412-
// modules.txt drift is the audit's below: one report per cause.
413-
let no_texts = HashMap::new();
414-
for (purl, record) in &manifest.patches {
415-
if in_scope.contains(purl) {
416-
if let Err(detail) =
417-
socket_patch_core::vendor::go_consumer_sync::verify_native_vendor_tree(
418-
&args.common.cwd,
419-
purl,
420-
record,
421-
&no_texts,
422-
)
423-
.await
424-
{
425-
drifts.push((
426-
purl.clone(),
427-
socket_patch_core::vendor::go_consumer_sync::NATIVE_VENDOR_CODE.into(),
428-
detail,
429-
));
430-
}
431-
}
432-
}
433410
// The redirects can be intact while the project no longer
434411
// builds: a committed vendor/modules.txt or the go.mod
435412
// requirements out of step with them (#343, #618).
436-
for issue in socket_patch_core::vendor::go_consumer_sync::audit(
413+
let issues = socket_patch_core::vendor::go_consumer_sync::audit(
437414
&args.common.cwd,
438415
&HashMap::new(),
439416
)
440-
.await
441-
{
417+
.await;
418+
// The native vendor tree a default build compiles must carry
419+
// every in-scope Go patch, vendor-owned ones included. A module
420+
// whose modules.txt drift the audit reports is skipped: one
421+
// report per cause.
422+
let stale: HashSet<&str> = issues
423+
.iter()
424+
.filter(|issue| {
425+
matches!(
426+
issue,
427+
socket_patch_core::vendor::go_consumer_sync::GoSyncIssue::VendorModulesTxt { .. }
428+
)
429+
})
430+
.map(|issue| issue.module())
431+
.collect();
432+
let no_texts = HashMap::new();
433+
for (purl, record) in &manifest.patches {
434+
if !in_scope.contains(purl)
435+
|| parse_golang_purl(purl).is_some_and(|(m, _)| stale.contains(m.as_ref()))
436+
{
437+
continue;
438+
}
439+
if let Err(detail) =
440+
socket_patch_core::vendor::go_consumer_sync::verify_native_vendor_tree(
441+
&args.common.cwd,
442+
purl,
443+
record,
444+
&no_texts,
445+
)
446+
.await
447+
{
448+
drifts.push((
449+
purl.clone(),
450+
socket_patch_core::vendor::go_consumer_sync::NATIVE_VENDOR_CODE.into(),
451+
detail,
452+
));
453+
}
454+
}
455+
for issue in &issues {
442456
drifts.push((
443457
issue.module().to_string(),
444458
issue.code().to_string(),

‎crates/socket-patch-core/src/patch/redirect/golang_local.rs‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,14 +199,16 @@ pub async fn reuse_go_redirect(
199199
}) {
200200
return None;
201201
}
202-
let refusal = crate::vendor::go_consumer_sync::replace_refusal(
202+
let mut refusal = crate::vendor::go_consumer_sync::replace_refusal(
203203
project_root,
204204
&module,
205205
&version,
206206
GO_PATCHES_DIR,
207207
)
208-
.await
209-
.or(selection_refusal(project_root, &module, &version).await);
208+
.await;
209+
if refusal.is_none() {
210+
refusal = selection_refusal(project_root, &module, &version).await;
211+
}
210212
if let Some(refusal) = refusal {
211213
return Some(synthesized_result(
212214
purl,
@@ -531,7 +533,14 @@ pub async fn reconcile_go_redirects(
531533
)
532534
.await
533535
{
534-
let purl = purl.unwrap_or_else(|| entry.module.clone());
536+
// A version-less directive names its version in the copy
537+
// path; a failure still needs a PURL to report.
538+
let purl = purl
539+
.or_else(|| {
540+
let (_, v) = entry.path.as_deref()?.rsplit_once('@')?;
541+
Some(build_golang_purl(&entry.module, v))
542+
})
543+
.unwrap_or_else(|| entry.module.clone());
535544
out.failed.push((
536545
purl.clone(),
537546
format!("kept the stale Go patch redirect for {purl}: {e}"),

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -282,12 +282,20 @@ pub async fn drop_replace_entry(
282282
.into_iter()
283283
.filter(|entry| entry.module == module && entry.owner == Some(owner))
284284
.collect();
285-
if !entries.is_empty() {
286-
// A dry run makes the same checks (a conflicting or invalid saved
287-
// checksum refuses) without writing go.sum, so it predicts the run.
288-
super::go_sum_backup::restore(project_root, &entries, dry_run).await?;
285+
// A dry run makes the same checks (a conflicting or invalid saved
286+
// checksum refuses) without writing go.sum, so it predicts the run.
287+
let sums = if entries.is_empty() {
288+
None
289+
} else {
290+
super::go_sum_backup::restore(project_root, &entries).await?
291+
};
292+
// go.sum changes only once the directive is gone, so a failed edit
293+
// leaves both as they were.
294+
let changed = remove_from_replace_files(project_root, module, owner, dry_run).await?;
295+
if let (Some(sums), false) = (sums, dry_run) {
296+
super::go_sum_backup::write(project_root, &sums).await?;
289297
}
290-
remove_from_replace_files(project_root, module, owner, dry_run).await
298+
Ok(changed)
291299
}
292300

293301
/// Remove `module`'s `owner` replace from the root `go.work` (when present)

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -698,8 +698,10 @@ fn omitted_by_build_constraint(text: &str) -> bool {
698698
}
699699
if !block {
700700
if let Some(expr) = go_build_expr(line) {
701+
// imports.ShouldBuild rejects a second `//go:build` line,
702+
// so `go mod vendor` leaves the file out.
701703
if modern.replace(expr).is_some() {
702-
return false;
704+
return true;
703705
}
704706
} else if legacy_allowed {
705707
if let Some(expr) = plus_build_expr(line) {
@@ -730,7 +732,8 @@ fn omitted_by_build_constraint(text: &str) -> bool {
730732
}
731733
}
732734
if let Some(expr) = modern {
733-
return constraint(expr) == Some(false);
735+
// An expression constraint.Parse rejects also leaves the file out.
736+
return constraint(expr) != Some(true);
734737
}
735738
legacy[..legacy_end]
736739
.iter()
@@ -912,6 +915,8 @@ mod tests {
912915
"// +build !",
913916
"// +build integration-test",
914917
"// +build linux,!!amd64 darwin-x",
918+
"//go:build linux &&",
919+
"//go:build linux\n//go:build darwin",
915920
] {
916921
assert!(
917922
omitted_by_build_constraint(&format!("{header}\n\npackage main\n")),

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

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,12 @@ fn backup(root: &Path, entry: &ReplaceEntry) -> Option<PathBuf> {
111111
/// A version the build no longer selects (go.mod now requires another one,
112112
/// or a pruned graph no longer has the module) needs no checksums: restoring
113113
/// them would leave go.sum untidy and make discovery see the old version
114-
/// again. `dry_run` makes every check without writing go.sum.
114+
/// again. Returns the restored go.sum without writing it (`None`: no
115+
/// change), so the caller writes it only once the directive is gone.
115116
pub(super) async fn restore(
116117
root: &Path,
117118
entries: &[ReplaceEntry],
118-
dry_run: bool,
119-
) -> Result<(), String> {
119+
) -> Result<Option<String>, String> {
120120
use super::go_build_list::{project_selection, Selection};
121121
let path = root.join("go.sum");
122122
let mut sums = read(&path).await?;
@@ -157,12 +157,14 @@ pub(super) async fn restore(
157157
sums = new;
158158
}
159159
}
160-
if !dry_run && sums != original {
161-
atomic_write_bytes_preserving_mode(&path, sums.as_bytes())
162-
.await
163-
.map_err(|e| e.to_string())?;
164-
}
165-
Ok(())
160+
Ok((sums != original).then_some(sums))
161+
}
162+
163+
/// Write the go.sum [`restore`] planned.
164+
pub(super) async fn write(root: &Path, sums: &str) -> Result<(), String> {
165+
atomic_write_bytes_preserving_mode(&root.join("go.sum"), sums.as_bytes())
166+
.await
167+
.map_err(|e| e.to_string())
166168
}
167169

168170
pub(crate) async fn forget(root: &Path, entries: &[ReplaceEntry]) {
@@ -319,6 +321,35 @@ mod tests {
319321
assert_eq!(read(&root.join("go.sum")).await.unwrap(), "");
320322
}
321323

324+
/// go.sum changes only after the directive is removed: a refused go.mod
325+
/// edit leaves both as they were.
326+
#[cfg(unix)]
327+
#[tokio::test]
328+
async fn failed_directive_edit_leaves_go_sum_unchanged() {
329+
let tmp = tempfile::tempdir().unwrap();
330+
let root = tmp.path();
331+
tokio::fs::write(
332+
root.join("go.mod"),
333+
"module consumer\ngo 1.21\nrequire example.com/upstream v1.0.0\n",
334+
)
335+
.await
336+
.unwrap();
337+
tokio::fs::write(root.join("go.sum"), SUMS).await.unwrap();
338+
ensure_replace_entry(root, MODULE, VERSION, GO_PATCHES_DIR, false)
339+
.await
340+
.unwrap();
341+
tokio::fs::write(root.join("go.sum"), "").await.unwrap();
342+
let real = tmp.path().join("real.mod");
343+
tokio::fs::rename(root.join("go.mod"), &real).await.unwrap();
344+
std::os::unix::fs::symlink(&real, root.join("go.mod")).unwrap();
345+
assert!(
346+
drop_replace_entry(root, MODULE, ReplaceOwner::GoPatches, false)
347+
.await
348+
.is_err()
349+
);
350+
assert_eq!(read(&root.join("go.sum")).await.unwrap(), "");
351+
}
352+
322353
#[tokio::test]
323354
async fn dry_run_drop_restores_nothing() {
324355
let tmp = tempfile::tempdir().unwrap();

0 commit comments

Comments
 (0)