Skip to content

Commit 7e00ddf

Browse files
author
v-HaripriyaC
committed
fix(go_modules): preserve unrelated go.mod checksums in go.sum
When Go tooling (go get, go mod tidy) runs during a dependency update, it can prune go.mod checksum entries for indirect/transitive dependencies that aren't strictly needed for building. This causes Dependabot PRs to unexpectedly remove hash entries from go.sum that go mod tidy would keep. Add reconcile_go_sum to detect go.mod checksum lines that were removed for modules NOT being updated, and restore them in sorted order. Fixes #14872
1 parent ef2d6a4 commit 7e00ddf

2 files changed

Lines changed: 118 additions & 2 deletions

File tree

go_modules/lib/dependabot/go_modules/file_updater/go_mod_updater.rb

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,10 +222,12 @@ def update_files
222222
substitute_all(substitutions.invert)
223223
end
224224

225-
updated_go_sum = original_go_sum ? File.read("go.sum") : nil
225+
updated_go_sum = T.let(nil, T.nilable(String))
226+
updated_go_sum = reconcile_go_sum(original_go_sum, File.read("go.sum")) if original_go_sum
227+
226228
updated_go_mod = File.read("go.mod")
227229

228-
{ go_mod: updated_go_mod, go_sum: updated_go_sum }
230+
{ go_mod: updated_go_mod, go_sum: updated_go_sum }.compact
229231
end
230232
end
231233

@@ -417,6 +419,49 @@ def build_module_stubs(stub_paths)
417419
end
418420
end
419421

422+
sig { params(original_go_sum: String, updated_go_sum: String).returns(String) }
423+
def reconcile_go_sum(original_go_sum, updated_go_sum)
424+
original_lines = original_go_sum.lines(chomp: true)
425+
updated_lines = updated_go_sum.lines(chomp: true)
426+
updated_set = updated_lines.to_set
427+
merged_lines = updated_lines.dup
428+
429+
original_lines.each do |line|
430+
next unless go_mod_checksum_line?(line)
431+
next if updated_set.include?(line)
432+
433+
module_identifier = go_sum_module_identifier(line)
434+
next unless module_identifier
435+
next if updated_dependency_names.include?(module_identifier)
436+
next unless zip_hash_still_present?(line, updated_set)
437+
438+
merged_lines << line
439+
end
440+
441+
merged_lines.sort!.join("\n") + "\n"
442+
end
443+
444+
sig { params(line: String).returns(T::Boolean) }
445+
def go_mod_checksum_line?(line)
446+
line.include?("/go.mod h1:")
447+
end
448+
449+
sig { params(line: String).returns(T.nilable(String)) }
450+
def go_sum_module_identifier(line)
451+
line.split(/\s+/, 2).first
452+
end
453+
454+
sig { params(go_mod_line: String, updated_set: T::Set[String]).returns(T::Boolean) }
455+
def zip_hash_still_present?(go_mod_line, updated_set)
456+
version_prefix = go_mod_line.sub(%r{/go\.mod\s.*}, "")
457+
updated_set.any? { |line| line.start_with?("#{version_prefix} h1:") }
458+
end
459+
460+
sig { returns(T::Set[String]) }
461+
def updated_dependency_names
462+
@updated_dependency_names ||= T.let(dependencies.to_set(&:name), T.nilable(T::Set[String]))
463+
end
464+
420465
# Given a go.mod file, find all `replace` directives pointing to a path
421466
# on the local filesystem, and return an array of pairs mapping the
422467
# original path to a hash of the path.

go_modules/spec/dependabot/go_modules/file_updater/go_mod_updater_spec.rb

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,77 @@ module github.com/dependabot/vgotest
276276
.not_to include(%(rsc.io/quote v1.4.0/go.mod h1:))
277277
end
278278

279+
context "when go tooling removes an unrelated go.mod checksum line" do
280+
let(:removed_line) do
281+
"gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E="
282+
end
283+
let(:zip_line) do
284+
"gonum.org/v1/gonum v0.16.0 h1:JKbmSgVMFkFMDpGCixMRJCMEMmNhrsJuJqVDPMGPnQY="
285+
end
286+
287+
it "restores the unrelated checksum line" do
288+
allow(File).to receive(:read).and_call_original
289+
290+
original_go_sum = fixture("projects", project_name, "go.sum") +
291+
"#{zip_line}\n#{removed_line}\n"
292+
updated_go_sum = original_go_sum.lines.reject { |line| line.chomp == removed_line }.join
293+
294+
allow(File).to receive(:read).with("go.sum").and_return(original_go_sum, updated_go_sum)
295+
296+
expect(updated_go_mod_content).to include(removed_line)
297+
end
298+
end
299+
300+
context "when the removed checksum belongs to the updated dependency" do
301+
let(:removed_line) { "rsc.io/quote v1.4.0/go.mod h1:omethingoldchecksum=" }
302+
303+
it "does not restore the removed checksum line" do
304+
allow(File).to receive(:read).and_call_original
305+
306+
original_go_sum = <<~GOSUM
307+
rsc.io/quote v1.4.0 h1:oldchecksum=
308+
#{removed_line}
309+
rsc.io/sampler v1.3.0/go.mod h1:anexistingchecksum=
310+
GOSUM
311+
312+
updated_go_sum = <<~GOSUM
313+
rsc.io/quote v1.5.2 h1:newchecksum=
314+
rsc.io/quote v1.5.2/go.mod h1:newgomodchecksum=
315+
rsc.io/sampler v1.3.0/go.mod h1:anexistingchecksum=
316+
GOSUM
317+
318+
allow(File).to receive(:read).with("go.sum").and_return(original_go_sum, updated_go_sum)
319+
320+
expect(updated_go_mod_content).not_to include(removed_line)
321+
end
322+
end
323+
324+
context "when a transitive dependency version is legitimately upgraded" do
325+
let(:removed_line) { "golang.org/x/sys v0.0.0-20200116001909/go.mod h1:oldtransitivechecksum=" }
326+
327+
it "does not restore the removed checksum line" do
328+
allow(File).to receive(:read).and_call_original
329+
330+
original_go_sum = <<~GOSUM
331+
golang.org/x/sys v0.0.0-20200116001909 h1:oldziphash=
332+
#{removed_line}
333+
rsc.io/quote v1.4.0 h1:oldchecksum=
334+
rsc.io/quote v1.4.0/go.mod h1:oldgomod=
335+
GOSUM
336+
337+
updated_go_sum = <<~GOSUM
338+
golang.org/x/sys v0.0.0-20220731174439 h1:newziphash=
339+
golang.org/x/sys v0.0.0-20220731174439/go.mod h1:newtransitivechecksum=
340+
rsc.io/quote v1.5.2 h1:newchecksum=
341+
rsc.io/quote v1.5.2/go.mod h1:newgomodchecksum=
342+
GOSUM
343+
344+
allow(File).to receive(:read).with("go.sum").and_return(original_go_sum, updated_go_sum)
345+
346+
expect(updated_go_mod_content).not_to include(removed_line)
347+
end
348+
end
349+
279350
describe "a non-existent dependency with a pseudo-version" do
280351
let(:project_name) { "non_existent_dependency" }
281352

0 commit comments

Comments
 (0)