Skip to content

x/mod/modfile, cmd/go: lines in exclude block don't use semver for sorting #60028

@dmitshur

Description

@dmitshur

I noticed that go mod tidy (in Go 1.20.4 and at tip) sorts lines in exclude blocks lexicographically. For some sequences of module versions it will cause a diff like this:

 exclude (
 	// ... some explanation for why this exclude is needed ...
 	example.com/module v0.1.0
+	example.com/module v1.10.0
+	example.com/module v1.11.0
+	example.com/module v1.12.0
 	example.com/module v1.3.0
 	example.com/module v1.5.0
 	example.com/module v1.6.0
 	example.com/module v1.6.1
 	example.com/module v1.7.0
-	example.com/module v1.10.0
-	example.com/module v1.11.0
-	example.com/module v1.12.0
 )

It's certainly not at all a big deal especially since exclude directives are rare, but it might still be slightly unexpected for it not to maintain the consistent semantic version sorting.

I took a quick look at what it might take to make it not do that. It turned out to be working as intended: all blocks (other than the newer retract one) sort lines via a simple lexicographic sort. It's just that exclude blocks are a rare situation where multiple versions of the same module may happen in practice and run into this edge case.

The go command currently gates some accepted changes to go mod tidy behavior on the Go language version specified in go.mod (e.g., tidyGoModSumVersionV = "v1.21") to avoid introducing unnecessary churn. (I think the concern of invalidating checksums doesn't apply to tidy behavior changes?) It'd also be possible to check what percentage of go.mod files in a corpus of public modules would be affected, though that misses private modules where exclude might be used more.

So fixing this or any other potential formatting change deemed worthwhile can also begin to apply upon reaching a future Go version. It probably belongs in package modfile since there's no intended change to meaning.

A formatting change that is gated behind Go version (current + 2) means by the time it happens all supported Go toolchains would handle it consistently. But since go mod tidy in Go 1.N refuses to tidy a go.mod with Go 1.(N+1), maybe just current + 1 would be sufficient?

I've sketched a possible implementation of this to see what it might look like, and perhaps it's not worth the implementation complexity (that needs to stay around) for such a small improvement to one block type. Maybe it's better to wait and solve this in bulk with some other minor changes, or when some bigger change happens and makes this obsolete. I'll let package owners chime in on if you think it's worth doing anything about this or just leaving it.

CC @bcmills, @rsc.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions