From 32a4c9466d07953ef752f28ce0d162f2c5abfaec Mon Sep 17 00:00:00 2001 From: Floze <88098863+floze-the-genius@users.noreply.github.com> Date: Sat, 18 Jul 2026 21:43:51 +0400 Subject: [PATCH] fix(sync): prefer repository replacements over matching AUR upgrades (#2910) fix upgrade handling for repo replacements --- pkg/db/executor.go | 1 + pkg/db/ialpm/alpm.go | 8 +++++ pkg/db/mock/executor.go | 12 +++++++ pkg/db/mock/repo.go | 3 +- pkg/upgrade/service.go | 42 ++++++++++++++++++++---- pkg/upgrade/service_test.go | 64 +++++++++++++++++++++++++++++++++++++ 6 files changed, 122 insertions(+), 8 deletions(-) diff --git a/pkg/db/executor.go b/pkg/db/executor.go index 793f8188..d343b9c5 100644 --- a/pkg/db/executor.go +++ b/pkg/db/executor.go @@ -54,6 +54,7 @@ type Executor interface { PackageGroups(IPackage) []string PackageOptionalDepends(IPackage) []Depend PackageProvides(IPackage) []Depend + PackageReplaces(IPackage) []Depend PackagesFromGroup(string) []IPackage PackagesFromGroupAndDB(string, string) ([]IPackage, error) RefreshHandle() error diff --git a/pkg/db/ialpm/alpm.go b/pkg/db/ialpm/alpm.go index 0bf8d900..54a9b599 100644 --- a/pkg/db/ialpm/alpm.go +++ b/pkg/db/ialpm/alpm.go @@ -415,6 +415,14 @@ func (ae *AlpmExecutor) PackageProvides(pkg alpm.Package) []alpm.Depend { return pkg.Provides() } +func (ae *AlpmExecutor) PackageReplaces(pkg alpm.Package) []alpm.Depend { + if pkgWithReplaces, ok := pkg.(interface{ Replaces() []alpm.Depend }); ok { + return pkgWithReplaces.Replaces() + } + + return nil +} + func (ae *AlpmExecutor) PackageGroups(pkg alpm.Package) []string { return pkg.Groups() } diff --git a/pkg/db/mock/executor.go b/pkg/db/mock/executor.go index 63943704..40aefcc1 100644 --- a/pkg/db/mock/executor.go +++ b/pkg/db/mock/executor.go @@ -27,6 +27,7 @@ type DBExecutor struct { PackageDependsFn func(IPackage) []Depend PackageOptionalDependsFn func(alpm.Package) []alpm.Depend PackageProvidesFn func(IPackage) []Depend + PackageReplacesFn func(IPackage) []Depend PackagesFromGroupFn func(string) []IPackage PackagesFromGroupAndDBFn func(string, string) ([]IPackage, error) RefreshHandleFn func() error @@ -134,6 +135,17 @@ func (t *DBExecutor) PackageProvides(iPackage IPackage) []Depend { panic("implement me") } +func (t *DBExecutor) PackageReplaces(iPackage IPackage) []Depend { + if t.PackageReplacesFn != nil { + return t.PackageReplacesFn(iPackage) + } + if pkg, ok := iPackage.(interface{ Replaces() []alpm.Depend }); ok { + return pkg.Replaces() + } + + return nil +} + func (t *DBExecutor) PackagesFromGroup(s string) []IPackage { if t.PackagesFromGroupFn != nil { return t.PackagesFromGroupFn(s) diff --git a/pkg/db/mock/repo.go b/pkg/db/mock/repo.go index 83ccecb6..68d475f7 100644 --- a/pkg/db/mock/repo.go +++ b/pkg/db/mock/repo.go @@ -25,6 +25,7 @@ type Package struct { PReason alpm.PkgReason PDepends DependList PProvides DependList + PReplaces DependList PArchitecture string } @@ -164,7 +165,7 @@ func (p *Package) Origin() alpm.PkgFrom { // Replaces returns a DependList with the packages this package replaces. func (p *Package) Replaces() []alpm.Depend { - panic("not implemented") + return p.PReplaces.Depends } // URL returns the upstream URL of the package. diff --git a/pkg/upgrade/service.go b/pkg/upgrade/service.go index 482c1456..a93abc81 100644 --- a/pkg/upgrade/service.go +++ b/pkg/upgrade/service.go @@ -64,15 +64,35 @@ func (u *UpgradeService) upGraph(ctx context.Context, graph *topo.Graph[string, filter Filter, ) (err error) { var ( - develUp UpSlice - errs []error - aurdata = make(map[string]*aur.Pkg) - aurUp UpSlice + develUp UpSlice + errs []error + aurdata = make(map[string]*aur.Pkg) + aurUp UpSlice + syncUpgrades map[string]db.SyncUpgrade ) remote := u.dbExecutor.InstalledRemotePackages() remoteNames := u.dbExecutor.InstalledRemotePackageNames() + if u.cfg.Mode.AtLeastRepo() { + syncUpgrades, err = u.dbExecutor.SyncUpgrades(enableDowngrade) + errs = append(errs, err) + + replaced := u.syncReplacedPackageNames(syncUpgrades) + if replaced.Cardinality() > 0 { + filteredRemote := make(map[string]db.IPackage, len(remote)) + for name, pkg := range remote { + if !replaced.Contains(name) { + filteredRemote[name] = pkg + } + } + remote = filteredRemote + remoteNames = slices.DeleteFunc(slices.Clone(remoteNames), func(name string) bool { + return replaced.Contains(name) + }) + } + } + if u.cfg.Mode.AtLeastAUR() { u.log.OperationInfoln(gotext.Get("Searching AUR for updates...")) @@ -168,7 +188,6 @@ func (u *UpgradeService) upGraph(ctx context.Context, graph *topo.Graph[string, if u.cfg.Mode.AtLeastRepo() { u.log.OperationInfoln(gotext.Get("Searching databases for updates...")) - syncUpgrades, err := u.dbExecutor.SyncUpgrades(enableDowngrade) for _, up := range syncUpgrades { if filter != nil && !filter(&db.Upgrade{ Name: up.Package.Name(), @@ -184,13 +203,22 @@ func (u *UpgradeService) upGraph(ctx context.Context, graph *topo.Graph[string, upgradeInfo := up graph = u.grapher.GraphSyncPkg(ctx, graph, up.Package, &upgradeInfo) } - - errs = append(errs, err) } return errors.Join(errs...) } +func (u *UpgradeService) syncReplacedPackageNames(syncUpgrades map[string]db.SyncUpgrade) mapset.Set[string] { + replaced := mapset.NewThreadUnsafeSet[string]() + for _, up := range syncUpgrades { + for _, replacement := range u.dbExecutor.PackageReplaces(up.Package) { + replaced.Add(replacement.Name) + } + } + + return replaced +} + func (u *UpgradeService) graphToUpSlice(graph *topo.Graph[string, *dep.InstallInfo]) (aurUp, repoUp UpSlice) { aurUp = UpSlice{Up: make([]Upgrade, 0, graph.Len())} repoUp = UpSlice{Up: make([]Upgrade, 0, graph.Len()), Repos: u.dbExecutor.Repos()} diff --git a/pkg/upgrade/service_test.go b/pkg/upgrade/service_test.go index ceae99de..9df30c77 100644 --- a/pkg/upgrade/service_test.go +++ b/pkg/upgrade/service_test.go @@ -759,6 +759,70 @@ func TestUpgradeService_GraphUpgradesNoUpdates(t *testing.T) { } } +func TestUpgradeService_GraphUpgradesPrefersSyncReplacementOverAUR(t *testing.T) { + t.Parallel() + + coreDB := mock.NewDB("extra") + dbExe := &mock.DBExecutor{ + InstalledRemotePackageNamesFn: func() []string { + return []string{"sdl2"} + }, + InstalledRemotePackagesFn: func() map[string]mock.IPackage { + return map[string]mock.IPackage{ + "sdl2": &mock.Package{ + PName: "sdl2", + PBase: "sdl2", + PVersion: "2.30.11-1", + PReason: alpm.PkgReasonDepend, + }, + } + }, + SyncUpgradesFn: func(bool) (map[string]db.SyncUpgrade, error) { + return map[string]db.SyncUpgrade{ + "sdl2-compat": { + Package: &mock.Package{ + PName: "sdl2-compat", + PVersion: "2.32.50-1", + PReason: alpm.PkgReasonDepend, + PDB: coreDB, + PReplaces: mock.DependList{Depends: []alpm.Depend{ + {Name: "sdl2"}, + }}, + }, + LocalVersion: "-", + Reason: alpm.PkgReasonDepend, + }, + }, nil + }, + ReposFn: func() []string { return []string{"extra"} }, + } + + mockAUR := &mockaur.MockAUR{ + GetFn: func(_ context.Context, query *aur.Query) ([]aur.Pkg, error) { + assert.Empty(t, query.Needles) + return []aur.Pkg{}, nil + }, + } + + logger := text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), true, "test") + grapher := dep.NewGrapher(dbExe, mockAUR, false, true, false, false, false, logger) + service := &UpgradeService{ + log: logger, + grapher: grapher, + aurCache: mockAUR, + dbExecutor: dbExe, + vcsStore: &vcs.Mock{}, + cfg: &settings.Configuration{Mode: parser.ModeAny}, + AURWarnings: query.NewWarnings(logger), + } + + graph, err := service.GraphUpgrades(t.Context(), nil, false, func(*Upgrade) bool { return true }) + require.NoError(t, err) + assert.False(t, graph.Exists("sdl2")) + require.True(t, graph.Exists("sdl2-compat")) + assert.Equal(t, dep.Sync, graph.GetNodeInfo("sdl2-compat").Value.Source) +} + func TestUpgradeService_UserExcludeUpgradesWithoutLuaHookUsesNativeMenu(t *testing.T) { t.Parallel() graph := newUpgradeSelectTestGraph(t)