diff --git a/internal/selfupdate/updater.go b/internal/selfupdate/updater.go index cc0708f..53dacf2 100644 --- a/internal/selfupdate/updater.go +++ b/internal/selfupdate/updater.go @@ -168,7 +168,7 @@ func (u *Updater) plan(ctx context.Context, method Method, report Report, opts O return plan, nil } - plan.Asset = u.chooseReleaseAsset(ctx, report, opts, tag) + plan.Asset = u.chooseReleaseAsset(ctx, report, tag) exists, err := u.Client.AssetExists(ctx, tag, plan.Asset.Name) if err != nil { @@ -183,10 +183,13 @@ func (u *Updater) plan(ctx context.Context, method Method, report Report, opts O // chooseReleaseAsset picks the package on macOS only when the running binary // lives where the package installs; otherwise the universal zip updates the -// binary in place when the release carries it. -func (u *Updater) chooseReleaseAsset(ctx context.Context, report Report, opts Options, tag string) Asset { +// binary in place when the release carries it. -force does not change the +// choice: reinstalling an equal version in place must not suddenly require +// root. Only when the release has no zip does -force fall through to the +// package, which then installs to PkgInstallDir as the target guard states. +func (u *Updater) chooseReleaseAsset(ctx context.Context, report Report, tag string) Asset { asset := report.ReleaseAsset - if asset.Kind != KindPkg || opts.Force || sameDir(filepath.Dir(report.ExecutablePath), PkgInstallDir) { + if asset.Kind != KindPkg || sameDir(filepath.Dir(report.ExecutablePath), PkgInstallDir) { return asset } diff --git a/internal/selfupdate/updater_test.go b/internal/selfupdate/updater_test.go index e8b658e..f13553a 100644 --- a/internal/selfupdate/updater_test.go +++ b/internal/selfupdate/updater_test.go @@ -311,6 +311,32 @@ func TestUpdaterDarwinZipWhenNotInUsrLocalBin(t *testing.T) { } } +// TestUpdaterDarwinForceReinstallsInPlace covers `update -force -version ` +// from a go-installed copy: the universal zip must be used in place, without +// root and without the installer. Found by the first real run against v0.2.0. +func TestUpdaterDarwinForceReinstallsInPlace(t *testing.T) { + rig := newRig(t, "darwin", "v0.2.0") + z := DarwinZipAsset() + rig.gh.addArchive("v0.2.0", z.Name, z.ChecksumName, z.InnerName, []byte("same-version-fresh-copy")) + rig.gh.addAsset("v0.2.0", "machineid-darwin-universal.pkg", []byte("pkg")) + rig.exec.on("codesign --verify", ExecResult{}) + rig.exec.on(rig.target+" -version", ExecResult{Stdout: "machineid v0.2.0"}) + + res, err := rig.u.Run(context.Background(), Options{CurrentVersion: "v0.2.0", Version: "v0.2.0", Force: true, AssumeYes: true}) + if err != nil { + t.Fatalf("%v\n%s", err, rig.out.String()) + } + if !res.Changed { + t.Errorf("-force should reinstall an equal version: %+v", res) + } + if got := mustRead(t, rig.target); string(got) != "same-version-fresh-copy" { + t.Errorf("target = %q", got) + } + if rig.exec.called("installer -pkg") { + t.Error("the package must not be used when the zip exists, even with -force") + } +} + func TestUpdaterDarwinNoZipRefusesOutsidePkgDir(t *testing.T) { rig := newRig(t, "darwin", "v0.3.0") rig.gh.addAsset("v0.3.0", "machineid-darwin-universal.pkg", []byte("pkg"))