From b4c82e9cd3d6c8bf2e75c9b80acef8c3d8873c92 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 20:24:16 +0000 Subject: [PATCH 1/3] Start fix for #1424 Assisted-by: Claude Code:claude-opus-5-5 From 1994b4da0f219df43a367e30708a8bd3c3993271 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 20:34:53 +0000 Subject: [PATCH 2/3] Speed up Go rescan on large go.sum files Hosted Go scans got about a third slower in #1403. For each patched dependency the rewriter asks go.sum which versions the build loads, and that tokenized every go.sum line every time. On a project with 1200 modules and 30 patches this was about 30% of the scan's work. Lines that can't belong to the module are now skipped with one prefix check before tokenizing, so the answer stays the same. Tests pin the edge cases: shared path prefixes, /go.mod lines, odd whitespace and short lines. Fixes #1424 Assisted-by: Claude Code:claude-opus-5-5 --- .../src/vendor/go_sum_edit.rs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/crates/socket-patch-core/src/vendor/go_sum_edit.rs b/crates/socket-patch-core/src/vendor/go_sum_edit.rs index 6c5fcceb4..130967b59 100644 --- a/crates/socket-patch-core/src/vendor/go_sum_edit.rs +++ b/crates/socket-patch-core/src/vendor/go_sum_edit.rs @@ -293,8 +293,17 @@ impl GoSumEditor { /// module tree) line for, in file order — the versions whose packages /// the build loads, unlike the `/go.mod` lines go writes for every /// version MVS reads. + /// + /// The hosted Go rewriter calls this once per patched dep, so a line + /// whose first token can't be `module` is skipped with one prefix + /// compare instead of being tokenized (#1424). pub(crate) fn zip_versions(&self, module: &str) -> Vec { self.current_lines() + .filter(|l| { + l.trim_start() + .strip_prefix(module) + .is_some_and(|rest| rest.starts_with(char::is_whitespace)) + }) .flat_map(go_sum_lines) .filter_map(|l| l.zip_version_of(module).map(str::to_string)) .collect() @@ -817,4 +826,45 @@ mod tests { } } } + + #[test] + fn zip_versions_reads_only_the_modules_own_zip_lines() { + let content = "example.com/a v1.0.0 h1:A=\n\ + example.com/a v1.1.0/go.mod h1:B=\n\ + example.com/ab v9.0.0 h1:C=\n\ + example.com/a/v2 v2.0.0 h1:D=\n\ + \t example.com/a v1.2.0\th1:E=\n\ + example.com/a v1.3.0\n\ + example.com/a\n\ + \n\ + example.com/a v1.4.0 h1:F= extra\n\ + Example.com/a v1.5.0 h1:G=\n\ + example.com/a v1.0.0 h1:A=\r\n"; + let want = ["v1.0.0", "v1.2.0", "v1.4.0", "v1.0.0"]; + let mut editor = GoSumEditor::new(content.to_string()); + assert_eq!(editor.zip_versions("example.com/a"), want); + assert_eq!(editor.zip_versions("example.com/ab"), ["v9.0.0"]); + assert_eq!(editor.zip_versions("example.com/a/v2"), ["v2.0.0"]); + assert!(editor.zip_versions("example.com").is_empty()); + assert!(editor.zip_versions("v1.0.0").is_empty()); + // Same answer once an edit has split the content into lines. + assert!(editor.upsert_module_lines(MOD, VER, ZIP_H1, GOMOD_H1)); + assert_eq!(editor.zip_versions("example.com/a"), want); + assert_eq!(editor.zip_versions(MOD), [VER]); + } + + #[test] + fn zip_versions_matches_the_full_line_reader() { + // The prefilter must never change the answer of tokenizing every + // line (#1424): compare against `go_sum_lines` over odd spacing. + let content = + "m v1 h1:a\n m v2 h\n\u{a0}m v3 h\nm\u{a0}v4 h\nmm v5 h\nm v6/go.mod h\nm\tv7\th\n"; + let editor = GoSumEditor::new(content.to_string()); + for module in ["m", "mm", "m\u{a0}v4", ""] { + let reference: Vec = go_sum_lines(content) + .filter_map(|l| l.zip_version_of(module).map(str::to_string)) + .collect(); + assert_eq!(editor.zip_versions(module), reference, "module {module:?}"); + } + } } From cb8bd650534c74ffb6bd07be8682f22386a28afc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 21:40:05 +0000 Subject: [PATCH 3/3] Parse each go.sum line once in zip_versions Lines that pass the module prefilter are now parsed directly instead of going back through the multi-line reader. The equivalence test now also checks the editor after an edit, when it holds split lines. Refs #1424 Assisted-by: Claude Code:claude-opus-5-5 --- .../src/vendor/go_sum_edit.rs | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/crates/socket-patch-core/src/vendor/go_sum_edit.rs b/crates/socket-patch-core/src/vendor/go_sum_edit.rs index 130967b59..63c4b34a6 100644 --- a/crates/socket-patch-core/src/vendor/go_sum_edit.rs +++ b/crates/socket-patch-core/src/vendor/go_sum_edit.rs @@ -304,7 +304,7 @@ impl GoSumEditor { .strip_prefix(module) .is_some_and(|rest| rest.starts_with(char::is_whitespace)) }) - .flat_map(go_sum_lines) + .filter_map(parse_go_sum_line) .filter_map(|l| l.zip_version_of(module).map(str::to_string)) .collect() } @@ -397,20 +397,24 @@ impl<'a> GoSumLine<'a> { /// lines are skipped. Each caller keeps its own token rule /// ([`GoSumLine::extra_tokens`]) and hash filter. pub(crate) fn go_sum_lines(text: &str) -> impl Iterator> { - text.lines().filter_map(|line| { - let mut tokens = line.split_whitespace(); - let (module, version, hash) = (tokens.next()?, tokens.next()?, tokens.next()?); - let (version, go_mod) = match version.strip_suffix("/go.mod") { - Some(version) => (version, true), - None => (version, false), - }; - Some(GoSumLine { - module, - version, - go_mod, - hash, - extra_tokens: tokens.next().is_some(), - }) + text.lines().filter_map(parse_go_sum_line) +} + +/// One line of [`go_sum_lines`], or `None` when it has fewer than three +/// tokens. +fn parse_go_sum_line(line: &str) -> Option> { + let mut tokens = line.split_whitespace(); + let (module, version, hash) = (tokens.next()?, tokens.next()?, tokens.next()?); + let (version, go_mod) = match version.strip_suffix("/go.mod") { + Some(version) => (version, true), + None => (version, false), + }; + Some(GoSumLine { + module, + version, + go_mod, + hash, + extra_tokens: tokens.next().is_some(), }) } @@ -859,12 +863,20 @@ mod tests { // line (#1424): compare against `go_sum_lines` over odd spacing. let content = "m v1 h1:a\n m v2 h\n\u{a0}m v3 h\nm\u{a0}v4 h\nmm v5 h\nm v6/go.mod h\nm\tv7\th\n"; - let editor = GoSumEditor::new(content.to_string()); + let text = GoSumEditor::new(content.to_string()); + // An edit leaves the editor holding split lines; ask both forms. + let mut lines = GoSumEditor::new(content.to_string()); + assert!(lines.upsert_module_lines(MOD, VER, ZIP_H1, GOMOD_H1)); for module in ["m", "mm", "m\u{a0}v4", ""] { let reference: Vec = go_sum_lines(content) .filter_map(|l| l.zip_version_of(module).map(str::to_string)) .collect(); - assert_eq!(editor.zip_versions(module), reference, "module {module:?}"); + assert_eq!(text.zip_versions(module), reference, "module {module:?}"); + assert_eq!( + lines.zip_versions(module), + reference, + "lines, module {module:?}" + ); } } }