fix(34): CR-01 never move a pseudo-version install back to its base release
plugin:update dropped a go.mod require that was not a strict vX.Y.Z and treated it as v0.0.0, so a module at a commit after v0.4.0 (v0.4.1-0.<ts>-<hash>) was moved back to v0.4.0. kiosk.Newer now places a release against a release, a git describe string or a pseudo-version (by the tag it builds on, in Go's version order); plugin:update and the admin Plugins screen badge share it. A pseudo-version with no tag before its commit is left alone with a note naming go get <module>@<tag>.
This commit is contained in:
@@ -342,13 +342,14 @@ func (in *installer) installedSatisfies(id, min string) (bool, error) {
|
||||
|
||||
// installedVersion returns the installed release of the plugin package
|
||||
// pkg (see installedSource): the module version of a module install, the
|
||||
// base tag of git describe for a submodule, "" for an in-app plugin.
|
||||
// base tag of git describe for a submodule, "" for an in-app plugin or a
|
||||
// module at a pseudo-version.
|
||||
func installedVersion(ctx context.Context, appDir, pkg string) (string, error) {
|
||||
src, err := installedSource(ctx, appDir, pkg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return src.Version, nil
|
||||
return releaseOrEmpty(src.Version), nil
|
||||
}
|
||||
|
||||
func sortedKeys(m kiosk.Requires) []string {
|
||||
|
||||
@@ -377,3 +377,64 @@ func TestAskToProceed(t *testing.T) {
|
||||
t.Errorf("accepted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdatePseudoVersion pins CR-01: a module required at a
|
||||
// pseudo-version (a commit after a release, as go get M@main leaves it) is
|
||||
// never moved to the release it builds on or an older one, and one with no
|
||||
// release before its commit is not moved at all.
|
||||
func TestUpdatePseudoVersion(t *testing.T) {
|
||||
const after040 = "v0.4.1-0.20261001120000-abcdef123456"
|
||||
plan := func(t *testing.T, installed string, releases ...string) (string, error) {
|
||||
t.Helper()
|
||||
reg := &memRegistry{}
|
||||
reg.add("acme.blog", nil, releases...)
|
||||
app := installApp(t, "v0.6.0", map[string]string{"acme.blog": installed})
|
||||
var out bytes.Buffer
|
||||
err := Update(t.Context(), UpdateOptions{
|
||||
StartDir: app, ID: "acme.blog", Registry: reg, Out: &out, Interactive: true,
|
||||
Confirm: func(string) (bool, error) { return false, nil },
|
||||
})
|
||||
return out.String(), err
|
||||
}
|
||||
|
||||
t.Run("only the base release and older", func(t *testing.T) {
|
||||
out, err := plan(t, after040, "v0.4.0:v0.6.0", "v0.3.0:v0.6.0")
|
||||
if err != nil {
|
||||
t.Fatalf("update: %v\n%s", err, out)
|
||||
}
|
||||
if !strings.Contains(out, "Nothing to update.") || !strings.Contains(out, "up to date at "+after040) || strings.Contains(out, "-> v0.4.0") {
|
||||
t.Errorf("a commit after v0.4.0 is moved back:\n%s", out)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("a newer release", func(t *testing.T) {
|
||||
out, err := plan(t, after040, "v0.4.1:v0.6.0", "v0.4.0:v0.6.0")
|
||||
if !errors.Is(err, ErrCancelled) {
|
||||
t.Fatalf("update: %v\n%s", err, out)
|
||||
}
|
||||
if !strings.Contains(out, "acme.blog "+after040+" -> v0.4.1 (module)") {
|
||||
t.Errorf("plan:\n%s", out)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no release before the commit", func(t *testing.T) {
|
||||
out, err := plan(t, "v0.0.0-20261001120000-abcdef123456", "v0.1.0:v0.6.0")
|
||||
if err != nil {
|
||||
t.Fatalf("update: %v\n%s", err, out)
|
||||
}
|
||||
if !strings.Contains(out, "Nothing to update.") || !strings.Contains(out, "plugin:update does not guess") || !strings.Contains(out, "go get example.test/blog@<tag>") {
|
||||
t.Errorf("an unplaceable commit is moved:\n%s", out)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("a requirement at a pseudo-version", func(t *testing.T) {
|
||||
reg := &memRegistry{}
|
||||
reg.add("acme.shop", kiosk.Requires{"acme.user": "v0.1.2"}, "v0.1.0:v0.6.0")
|
||||
app := installApp(t, "v0.6.0", map[string]string{"acme.user": "v0.1.3-0.20261001120000-abcdef123456"})
|
||||
var out bytes.Buffer
|
||||
p, err := PlanInstall(t.Context(), InstallOptions{StartDir: app, Arg: "acme.shop", Yes: true, Registry: reg, Out: &out})
|
||||
if err != nil || planSteps(p) != "acme.shop@v0.1.0" || !strings.Contains(out.String(), "cannot be read") {
|
||||
t.Errorf("plan: %v, %v\n%s", p, err, out.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -45,8 +45,9 @@ type pluginSource struct {
|
||||
// Module is the module providing the package ("" for a package of the
|
||||
// application module).
|
||||
Module string
|
||||
// Version is the module version (module), the base release tag of
|
||||
// git describe (submodule) or "".
|
||||
// Version is the go.mod module version (module: a release or a
|
||||
// pseudo-version, see kiosk.Newer), the base release tag of git
|
||||
// describe (submodule) or "".
|
||||
Version string
|
||||
// Dir is the module root of a submodule or local plugin.
|
||||
Dir string
|
||||
@@ -116,10 +117,9 @@ func installedSource(ctx context.Context, appDir, pkg string) (pluginSource, err
|
||||
apply(gomod.Replace, appDir)
|
||||
apply(work.Replace, workDir)
|
||||
if src.Dir == "" {
|
||||
// The version stays as go.mod has it: a pseudo-version is a commit
|
||||
// after a release, and only kiosk.Newer may compare it (CR-01).
|
||||
src.Kind = ModeModule
|
||||
if !kiosk.ValidVersion(src.Version) {
|
||||
src.Version = ""
|
||||
}
|
||||
return src, nil
|
||||
}
|
||||
if !isOwnRepository(ctx, src.Dir) {
|
||||
@@ -131,6 +131,16 @@ func installedSource(ctx context.Context, appDir, pkg string) (pluginSource, err
|
||||
return src, nil
|
||||
}
|
||||
|
||||
// releaseOrEmpty returns v when it is a vX.Y.Z release and "" otherwise:
|
||||
// a requirement check accepts a version it cannot read (a pseudo-version)
|
||||
// rather than refuse it.
|
||||
func releaseOrEmpty(v string) string {
|
||||
if kiosk.ValidVersion(v) {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// updateStep is one plugin plugin:update moves.
|
||||
type updateStep struct {
|
||||
ID, Package string
|
||||
@@ -279,18 +289,17 @@ func (u *updater) registryReason() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// pick chooses the newest release above have whose framework minimum the
|
||||
// app satisfies, with a note naming a still newer release that needs a
|
||||
// newer framework. When there is none it returns only a note: the newer
|
||||
// release that needs a newer framework, or that have is the newest.
|
||||
// pick chooses the newest release above have (kiosk.Newer: never at or
|
||||
// below the release a pseudo-version or describe string builds on) whose
|
||||
// framework minimum the app satisfies, with a note naming a still newer
|
||||
// release that needs a newer framework. An empty have (a submodule with no
|
||||
// release tag behind its commit) takes any release. When there is none it
|
||||
// returns only a note: the newer release that needs a newer framework, or
|
||||
// that have is the newest.
|
||||
func (u *updater) pick(versions []kiosk.RegistryVersion, have string) (kiosk.RegistryVersion, string) {
|
||||
base := have
|
||||
if base == "" {
|
||||
base = "v0.0.0"
|
||||
}
|
||||
var newer []kiosk.RegistryVersion
|
||||
for _, v := range versions {
|
||||
if kiosk.ValidVersion(v.Version) && kiosk.CompareVersions(v.Version, base) > 0 {
|
||||
if kiosk.ValidVersion(v.Version) && (have == "" || kiosk.Newer(v.Version, have)) {
|
||||
newer = append(newer, v)
|
||||
}
|
||||
}
|
||||
@@ -313,6 +322,12 @@ func (u *updater) planModule(p Plugin, src pluginSource) {
|
||||
u.note(p.ID, "installed as a Go module, and "+why)
|
||||
return
|
||||
}
|
||||
if !kiosk.Comparable(src.Version) {
|
||||
// A commit with no release tag before it, or a pre-release: no
|
||||
// release is known to be newer, and guessing could go back.
|
||||
u.note(p.ID, fmt.Sprintf("installed at %s, which is not after a release; plugin:update does not guess, so pin a release with go get %s@<tag>", displayVersion(src.Version), src.Module))
|
||||
return
|
||||
}
|
||||
versions, err := u.opts.Registry.Versions(u.ctx, p.ID)
|
||||
if errors.Is(err, kiosk.ErrNotFound) {
|
||||
u.note(p.ID, "not in the registry")
|
||||
@@ -421,7 +436,7 @@ func (u *updater) checkRequires(m Manifest, installed map[string]pluginSource) {
|
||||
listed[p.ID] = p.Module
|
||||
}
|
||||
for id, src := range installed {
|
||||
final[id] = src.Version
|
||||
final[id] = releaseOrEmpty(src.Version)
|
||||
}
|
||||
for _, s := range u.steps {
|
||||
final[s.ID] = s.To.Version
|
||||
@@ -439,7 +454,7 @@ func (u *updater) checkRequires(m Manifest, installed map[string]pluginSource) {
|
||||
if !known {
|
||||
src, err := installedSource(u.ctx, u.appDir, listed[dep])
|
||||
if err == nil {
|
||||
have = src.Version
|
||||
have = releaseOrEmpty(src.Version)
|
||||
}
|
||||
}
|
||||
if have != "" && !kiosk.Satisfies(have, min) {
|
||||
|
||||
Reference in New Issue
Block a user