Skip to content

Commit

Permalink
fix: use latest version returned by goproxy instead of manually findi…
Browse files Browse the repository at this point in the history
…ng latest (#17)
  • Loading branch information
nieomylnieja authored Dec 7, 2023
1 parent bd06ea0 commit a9c2806
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 53 deletions.
84 changes: 47 additions & 37 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package libyear

import (
"context"
"errors"
"log"
"os"
"slices"
Expand Down Expand Up @@ -57,7 +58,7 @@ func (c Command) Run(ctx context.Context) error {
mainModule.Time = time.Now()
if !c.optionIsSet(OptionIncludeIndirect) {
// Filter out indirect.
modules = slices.DeleteFunc(modules, func(module *internal.Module) bool { return module.IsIndirect })
modules = slices.DeleteFunc(modules, func(module *internal.Module) bool { return module.Indirect })
}

group, _ := c.newErrGroup(ctx)
Expand All @@ -70,7 +71,7 @@ func (c Command) Run(ctx context.Context) error {
}
// Remove skipped modules.
if c.optionIsSet(OptionSkipFresh) {
modules = slices.DeleteFunc(modules, func(module *internal.Module) bool { return module.IsFresh })
modules = slices.DeleteFunc(modules, func(module *internal.Module) bool { return module.Skipped })
}

// Aggregate results for main module.
Expand All @@ -93,7 +94,21 @@ const secondsInYear = float64(365 * 24 * 60 * 60)

func (c Command) runForModule(module *internal.Module) error {
// We skip this module, unless we get to the end and manage to calculate libyear.
module.IsFresh = true
module.Skipped = true

// Fetch latest.
latest, err := c.repo.GetLatestInfo(module.Path)
if err != nil {
return err
}
// It returns -1 (smaller), 0 (larger), or 1 (greater) when compared.
if module.Version.Compare(latest.Version) != -1 {
module.Latest = module
module.Time = latest.Time
return nil
}
module.Latest = latest

// Since we're parsing the go.mod file directly, we might need to fetch the Module.Time.
if module.Time.IsZero() {
fetchedModule, err := c.repo.GetInfo(module.Path, module.Version)
Expand All @@ -102,65 +117,60 @@ func (c Command) runForModule(module *internal.Module) error {
}
module.Time = fetchedModule.Time
}
// Fetch all versions.

// The following calculations are based on https://ericbouwers.github.io/papers/icse15.pdf.
module.Libyear = calculateLibyear(module, latest)
if c.optionIsSet(OptionShowReleases) {
versions, err := c.getVersions(module)
if err == errNoVersions {
log.Printf("WARN: module '%s' does not have any versions", module.Path)
return nil
}
module.ReleasesDiff = calculateReleases(module, latest, versions)
}
if c.optionIsSet(OptionShowVersions) {
module.VersionsDiff = calculateVersions(module, latest)
}

module.Skipped = false
return nil
}

var errNoVersions = errors.New("no versions found")

func (c Command) getVersions(module *internal.Module) ([]*semver.Version, error) {
versions, err := c.repo.GetVersions(module.Path)
if err != nil {
return err
return nil, err
}
if len(versions) == 0 {
if module.Version.Prerelease() == "" {
log.Printf("WARN: module '%s' does not have any versions", module.Path)
return nil
return nil, errNoVersions
}
// Try fetching the versions from deps.dev.
// Go list does not list prerelease versions, which is fine,
// unless we're dealing with a prerelease version ourselves.
versions, err = c.fallbackVersions.GetVersions(module.Path)
if err != nil {
return err
return nil, err
}
// Check again.
if len(versions) == 0 {
log.Printf("WARN: module '%s' does not have any versions", module.Path)
return nil
return nil, errNoVersions
}
}
sort.Sort(semver.Collection(versions))

latestVersion := versions[len(versions)-1]
// It returns -1 (smaller), 0 (larger), or 1 (greater) when compared.
if module.Version.Compare(latestVersion) != -1 {
module.Latest = module
return nil
}
// Fetch latest.
latest, err := c.repo.GetInfo(module.Path, latestVersion)
if err != nil {
return err
}
module.Latest = latest

// The following calculations are based on https://ericbouwers.github.io/papers/icse15.pdf.
module.Libyear = calculateLibyear(module, latest)
if c.optionIsSet(OptionShowReleases) {
module.ReleasesDiff = calculateReleases(module, versions)
}
if c.optionIsSet(OptionShowVersions) {
module.VersionsDiff = calculateVersions(module, latest)
}

module.IsFresh = false
return nil
return versions, nil
}

func calculateLibyear(module, latest *internal.Module) float64 {
diff := latest.Time.Sub(module.Time)
return diff.Seconds() / secondsInYear
}

func calculateReleases(module *internal.Module, versions []*semver.Version) int {
func calculateReleases(module, latest *internal.Module, versions []*semver.Version) int {
currentIndex := slices.IndexFunc(versions, func(v *semver.Version) bool { return module.Version.Equal(v) })
latestIndex := len(versions) - 1
latestIndex := slices.IndexFunc(versions, func(v *semver.Version) bool { return latest.Version.Equal(v) })
// Example:
// v: v1 | v2 | v3 | v4
// i: 0 1 2 3 > len == 4
Expand Down
19 changes: 15 additions & 4 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,22 +69,27 @@ func TestCommand_calculateLibyear(t *testing.T) {
func TestCommand_calculateReleases(t *testing.T) {
tests := []struct {
CurrentVersion string
LatestVersion string
Versions []string
Expected int
}{
{
CurrentVersion: "v0.9.0",
LatestVersion: "v1.0.0",
Versions: []string{
"v0.9.0",
"v0.9.1",
"v0.9.2",
"v0.10.0",
"v1.0.0",
"v2.0.0-incompatible1",
"v2.0.0-incompatible2",
},
Expected: 4,
},
{
CurrentVersion: "v0.10.0",
LatestVersion: "v1.0.0",
Versions: []string{
"v0.9.1",
"v0.9.2",
Expand All @@ -95,6 +100,7 @@ func TestCommand_calculateReleases(t *testing.T) {
},
{
CurrentVersion: "v1.0.0",
LatestVersion: "v1.0.0",
Versions: []string{
"v0.9.2",
"v0.10.0",
Expand All @@ -104,6 +110,7 @@ func TestCommand_calculateReleases(t *testing.T) {
},
{
CurrentVersion: "v1.0.0",
LatestVersion: "v1.0.0",
Versions: []string{
"v1.0.0",
},
Expand All @@ -118,6 +125,7 @@ func TestCommand_calculateReleases(t *testing.T) {
}
actual := calculateReleases(
&internal.Module{Version: semver.MustParse(test.CurrentVersion)},
&internal.Module{Version: semver.MustParse(test.LatestVersion)},
versions)
assert.Equal(t, test.Expected, actual)
})
Expand Down Expand Up @@ -193,10 +201,11 @@ func TestCommand_Fallback(t *testing.T) {
semver.MustParse("v1.0.0"),
semver.MustParse("v2.0.0"),
},
getInfoResponse: &internal.Module{},
getInfoResponse: &internal.Module{},
getLatestResponse: &internal.Module{Version: semver.MustParse("v2.0.0")},
}
versionsGetter := &mockVersionsGetter{}
cmd := Command{repo: modulesRepo, fallbackVersions: versionsGetter}
cmd := Command{repo: modulesRepo, fallbackVersions: versionsGetter, opts: OptionShowReleases}

err := cmd.runForModule(&internal.Module{Version: semver.MustParse("v1.0.0")})

Expand All @@ -208,9 +217,10 @@ func TestCommand_Fallback(t *testing.T) {
modulesRepo := &mockModulesRepo{
getVersionsResponse: []*semver.Version{},
getInfoResponse: &internal.Module{},
getLatestResponse: &internal.Module{Version: semver.MustParse("v2.0.0")},
}
versionsGetter := &mockVersionsGetter{}
cmd := Command{repo: modulesRepo, fallbackVersions: versionsGetter}
cmd := Command{repo: modulesRepo, fallbackVersions: versionsGetter, opts: OptionShowReleases}

// Don't call fallback if a version does not contain a prerelease.
// We only expect GOPROXY to lack versions list when no semver version was released by a module.
Expand All @@ -232,6 +242,7 @@ type mockModulesRepo struct {
getVersionsCalledTimes int
getVersionsResponse []*semver.Version
getInfoResponse *internal.Module
getLatestResponse *internal.Module
}

func (m *mockModulesRepo) GetVersions(string) ([]*semver.Version, error) {
Expand All @@ -248,7 +259,7 @@ func (m *mockModulesRepo) GetInfo(string, *semver.Version) (*internal.Module, er
}

func (m *mockModulesRepo) GetLatestInfo(string) (*internal.Module, error) {
panic("implement me")
return m.getLatestResponse, nil
}

type mockVersionsGetter struct {
Expand Down
7 changes: 4 additions & 3 deletions internal/go_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,22 +49,23 @@ func (e *GoListExecutor) GetLatestInfo(path string) (*Module, error) {
return e.getInfo(path, nil, true)
}

// Fetch module details.
func (e *GoListExecutor) getInfo(path string, version *semver.Version, latest bool) (*Module, error) {
var versionStr string
if latest {
versionStr = "latest"
} else {
versionStr = version.String()
versionStr = "v" + version.String()
}
// Try loading from cache.
if e.cache != nil {
if version != nil && e.cache != nil {
m, loaded := e.cache.Load(path, version)
if loaded {
return m, nil
}
}
// Fetch module details.
out, err := e.exec(path + "@v" + versionStr)
out, err := e.exec(path + "@" + versionStr)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion internal/go_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func (c *GoProxyClient) GetLatestInfo(path string) (*Module, error) {

func (c *GoProxyClient) getInfo(path string, version *semver.Version, latest bool) (*Module, error) {
// Try loading from cache.
if c.cache != nil {
if version != nil && c.cache != nil {
m, loaded := c.cache.Load(path, version)
if loaded {
return m, nil
Expand Down
14 changes: 7 additions & 7 deletions internal/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ type Module struct {
Version *semver.Version `json:"Version"`
Time time.Time `json:"Time"`

IsIndirect bool `json:"-"`
IsFresh bool `json:"-"`
Latest *Module `json:"-"`
Libyear float64 `json:"-"`
Indirect bool `json:"-"`
Skipped bool `json:"-"`
Latest *Module `json:"-"`
Libyear float64 `json:"-"`
// ReleasesDiff is the number of release versions between latest and current.
ReleasesDiff int `json:"-"`
// VersionsDiff is an array of 3 elements: major, minor and patch versions.
Expand Down Expand Up @@ -64,9 +64,9 @@ func ReadGoMod(content []byte) (mainModule *Module, modules []*Module, err error
return nil, nil, err
}
modules = append(modules, &Module{
Path: require.Mod.Path,
Version: version,
IsIndirect: require.Indirect,
Path: require.Mod.Path,
Version: version,
Indirect: require.Indirect,
})
}
if modFile.Module == nil {
Expand Down
7 changes: 6 additions & 1 deletion test/inputs/responses.json
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,17 @@
"Version": "v1.3.2",
"Time": "2023-06-08T06:14:45Z"
},
"github.com/!burnt!sushi/toml": {
"Path": "github.com/BurntSushi/toml",
"Version": "v2.0.0-experimental",
"Time": "2021-08-05T05:14:45Z"
},
"github.com/!burnt!sushi/toml/@latest": {
"Path": "github.com/BurntSushi/toml",
"Version": "v1.3.2",
"Time": "2023-06-08T06:14:45Z"
},
"github.com/!burnt!sushi/toml/@v/list": "v0.1.0\nv0.2.0\nv0.3.0\nv0.3.1\nv0.4.0\nv0.4.1\nv1.0.0\nv1.1.0\nv1.2.0\nv1.2.1\nv1.3.0\nv1.3.1\nv1.3.2",
"github.com/!burnt!sushi/toml/@v/list": "v0.1.0\nv0.2.0\nv0.3.0\nv0.3.1\nv0.4.0\nv0.4.1\nv1.0.0\nv1.1.0\nv1.2.0\nv1.2.1\nv1.3.0\nv1.3.1\nv1.3.2\nv2.0.0-experimental",
"github.com/test/test/@latest": "v1.0.0",
"github.com/test/test/@v/v1.0.0.mod": "./test/inputs/go.mod"
}

0 comments on commit a9c2806

Please sign in to comment.