Skip to content

Commit

Permalink
ignore retracted versions (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
icholy authored Jul 7, 2024
1 parent 0cfd683 commit 653c960
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 17 deletions.
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module github.com/icholy/gomajor

go 1.15
go 1.18

require (
golang.org/x/mod v0.14.0
golang.org/x/sync v0.6.0
golang.org/x/mod v0.18.0
golang.org/x/sync v0.7.0
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
Expand All @@ -19,6 +21,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
135 changes: 121 additions & 14 deletions internal/modproxy/modproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import (
"net/http"
"os"
"path"
"slices"

Check failure on line 13 in internal/modproxy/modproxy.go

View workflow job for this annotation

GitHub Actions / build

package slices is not in GOROOT (/opt/hostedtoolcache/go/1.18.10/x64/src/slices)
"strconv"
"strings"

"golang.org/x/mod/modfile"
"golang.org/x/mod/module"
"golang.org/x/mod/semver"
"golang.org/x/sync/errgroup"
Expand All @@ -26,6 +28,19 @@ type Module struct {
Versions []string
}

// MaxVersionModule returns the latest version of the module in the list.
// If pre is false, pre-release versions will are excluded.
// Retracted versions are excluded.
func MaxVersionModule(mods []*Module, pre bool, r Retractions) (*Module, string) {
for i := len(mods); i > 0; i-- {
mod := mods[i-1].Retract(r)
if max := mod.MaxVersion("", pre); max != "" {
return mod, max
}
}
return nil, ""
}

// MaxVersion returns the latest version.
// If there are no versions, the empty string is returned.
// Prefix can be used to filter the versions based on a prefix.
Expand All @@ -44,6 +59,15 @@ func (m *Module) MaxVersion(prefix string, pre bool) string {
return max
}

// Retract returns a copy of m with the retracted versions removed.
func (m *Module) Retract(r Retractions) *Module {
versions := slices.Clone(m.Versions)
return &Module{
Path: m.Path,
Versions: slices.DeleteFunc(versions, r.Includes),
}
}

// IsNewerVersion returns true if newversion is greater than oldversion in terms of semver.
// If major is true, then newversion must be a major version ahead of oldversion to be considered newer.
func IsNewerVersion(oldversion, newversion string, major bool) bool {
Expand All @@ -58,32 +82,44 @@ func IsNewerVersion(oldversion, newversion string, major bool) bool {
// Invalid versions are considered lower than valid ones.
// If both versions are invalid, the empty string is returned.
func MaxVersion(v, w string) string {
// sort by validity
if !semver.IsValid(v) && !semver.IsValid(w) {
return ""
}
if CompareVersion(v, w) == 1 {
return v
}
return w
}

// CompareVersion returns -1, 0, or 1 if v is less than, equal to, or greater than w.
// Incompatible versions are considered lower than non-incompatible ones.
// Invalid versions are considered lower than valid ones.
// If both versions are invalid, the empty string is returned.
func CompareVersion(v, w string) int {
// sort by validity
vValid := semver.IsValid(v)
wValid := semver.IsValid(w)
if !vValid && !wValid {
return ""
return 0
}
if vValid != wValid {
if vValid {
return v
return 1
}
return w
return -1
}
// sort by compatibility
vIncompatible := strings.HasSuffix(semver.Build(v), "+incompatible")
wIncompatible := strings.HasSuffix(semver.Build(w), "+incompatible")
if vIncompatible != wIncompatible {
if wIncompatible {
return v
return 1
}
return w
return -1
}
// sort by semver
if semver.Compare(v, w) == 1 {
return v
}
return w
return semver.Compare(v, w)
}

// NextMajor returns the next major version after the provided version
Expand Down Expand Up @@ -174,13 +210,20 @@ func Latest(modpath string, cached, pre bool) (*Module, error) {
if err != nil {
return nil, err
}
for i := len(mods); i > 0; i-- {
mod := mods[i-1]
if max := mod.MaxVersion("", pre); max != "" {
return mod, nil
// find the retractions
var r Retractions
if mod, _ := MaxVersionModule(mods, false, nil); mod != nil {
var err error
r, err = FetchRetractions(mod)
if err != nil {
return nil, err
}
}
return nil, ErrNoVersions
mod, _ := MaxVersionModule(mods, pre, r)
if mod == nil {
return nil, ErrNoVersions
}
return mod, nil
}

// List finds all the major versions of a module
Expand Down Expand Up @@ -260,6 +303,70 @@ func QueryPackage(pkgpath string, cached bool) (*Module, error) {
return nil, fmt.Errorf("failed to find module for package: %s", pkgpath)
}

// FetchRetractions fetches the retractions for this module.
func FetchRetractions(mod *Module) (Retractions, error) {
max := mod.MaxVersion("", false)
if max == "" {
return nil, nil
}
escaped, err := module.EscapePath(mod.Path)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", "https://proxy.golang.org/"+escaped+"/@v/"+max+".mod", nil)
if err != nil {
return nil, err
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
if res.StatusCode != 200 {
msg := string(body)
if msg == "" {
msg = res.Status
}
return nil, fmt.Errorf("proxy: %s", msg)
}
file, err := modfile.ParseLax(mod.Path, body, nil)
if err != nil {
return nil, err
}
var retractions Retractions
for _, r := range file.Retract {
retractions = append(retractions, VersionRange{Low: r.Low, High: r.High})
}
return retractions, nil
}

// VersionRange is an inclusive version range.
type VersionRange struct {
Low, High string
}

// Includes reports whether v is in the inclusive range
func (r VersionRange) Includes(v string) bool {
return CompareVersion(v, r.Low) >= 0 && CompareVersion(v, r.High) <= 0
}

// Retractions is a list of retracted versions.
type Retractions []VersionRange

// Includes reports whether v is retracted
func (rr Retractions) Includes(v string) bool {
for _, r := range rr {
if r.Includes(v) {
return true
}
}
return false
}

// Update reports a newer version of a module.
// The Err field will be set if an error occured.
type Update struct {
Expand Down
58 changes: 58 additions & 0 deletions internal/modproxy/modproxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,61 @@ func TestIsNewerVersion(t *testing.T) {
})
}
}

func TestCompareVersion(t *testing.T) {
tests := []struct {
v, w string
want int
}{
{v: "v0.0.0", w: "v1.0.0", want: -1},
{v: "v1.0.0", w: "v0.0.0", want: 1},
{v: "v0.0.0", w: "v0.0.0", want: 0},
{v: "v12.0.0+incompatible", w: "v0.0.0", want: -1},
{v: "", w: "", want: 0},
{v: "v0.1.0", w: "bad", want: 1},
{v: "v0.0.0+incompatible", w: "v0.0.0", want: -1},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
if got := CompareVersion(tt.v, tt.w); got != tt.want {
t.Fatalf("CompareVersion(%q, %q) = %v, want %v", tt.v, tt.w, got, tt.want)
}
})
}
}

func TestVersionRange(t *testing.T) {
tests := []struct {
r VersionRange
v string
want bool
}{
{
r: VersionRange{Low: "v0.0.0", High: "v0.0.1"},
v: "v0.0.0",
want: true,
},
{
r: VersionRange{Low: "v0.0.0", High: "v0.0.1"},
v: "v0.0.1",
want: true,
},
{
r: VersionRange{Low: "v0.0.0", High: "v0.0.1"},
v: "v0.0.2",
want: false,
},
{
r: VersionRange{Low: "v0.0.0", High: "v0.0.1"},
v: "v0.0.0+incompatible",
want: false,
},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
if got := tt.r.Includes(tt.v); got != tt.want {
t.Fatalf("VersionRange{Low: %q, High: %q}.Includes(%q) = %v, want %v", tt.r.Low, tt.r.High, tt.v, got, tt.want)
}
})
}
}

0 comments on commit 653c960

Please sign in to comment.