yay/pkg/upgrade/service.go

297 lines
7.5 KiB
Go

package upgrade
import (
"context"
"sort"
"github.com/Jguer/aur"
"github.com/Jguer/go-alpm/v2"
mapset "github.com/deckarep/golang-set/v2"
"github.com/leonelquinteros/gotext"
"github.com/Jguer/yay/v12/pkg/db"
"github.com/Jguer/yay/v12/pkg/dep"
"github.com/Jguer/yay/v12/pkg/intrange"
"github.com/Jguer/yay/v12/pkg/multierror"
"github.com/Jguer/yay/v12/pkg/settings"
"github.com/Jguer/yay/v12/pkg/text"
"github.com/Jguer/yay/v12/pkg/topo"
"github.com/Jguer/yay/v12/pkg/vcs"
)
type UpgradeService struct {
grapher *dep.Grapher
aurCache aur.QueryClient
dbExecutor db.Executor
vcsStore vcs.Store
cfg *settings.Configuration
log *text.Logger
noConfirm bool
}
func NewUpgradeService(grapher *dep.Grapher, aurCache aur.QueryClient,
dbExecutor db.Executor, vcsStore vcs.Store,
cfg *settings.Configuration, noConfirm bool, logger *text.Logger,
) *UpgradeService {
return &UpgradeService{
grapher: grapher,
aurCache: aurCache,
dbExecutor: dbExecutor,
vcsStore: vcsStore,
cfg: cfg,
noConfirm: noConfirm,
log: logger,
}
}
// upGraph adds packages to upgrade to the graph.
func (u *UpgradeService) upGraph(ctx context.Context, graph *topo.Graph[string, *dep.InstallInfo],
enableDowngrade bool,
filter Filter,
) (err error) {
var (
develUp UpSlice
errs multierror.MultiError
aurdata = make(map[string]*aur.Pkg)
aurUp UpSlice
)
remote := u.dbExecutor.InstalledRemotePackages()
remoteNames := u.dbExecutor.InstalledRemotePackageNames()
if u.cfg.Mode.AtLeastAUR() {
u.log.OperationInfoln(gotext.Get("Searching AUR for updates..."))
_aurdata, err := u.aurCache.Get(ctx, &aur.Query{Needles: remoteNames, By: aur.Name})
errs.Add(err)
if err == nil {
for i := range _aurdata {
pkg := &_aurdata[i]
aurdata[pkg.Name] = pkg
}
aurUp = UpAUR(u.log, remote, aurdata, u.cfg.TimeUpdate, enableDowngrade)
if u.cfg.Devel {
u.log.OperationInfoln(gotext.Get("Checking development packages..."))
develUp = UpDevel(ctx, u.log, remote, aurdata, u.vcsStore)
u.vcsStore.CleanOrphans(remote)
}
}
}
names := mapset.NewThreadUnsafeSet[string]()
for i := range develUp.Up {
up := &develUp.Up[i]
// check if deps are satisfied for aur packages
reason := dep.Explicit
if up.Reason == alpm.PkgReasonDepend {
reason = dep.Dep
}
if filter != nil && !filter(up) {
continue
}
aurPkg := aurdata[up.Name]
graph = u.grapher.GraphAURTarget(ctx, graph, aurPkg, &dep.InstallInfo{
Reason: reason,
Source: dep.AUR,
AURBase: &aurPkg.PackageBase,
Upgrade: true,
Devel: true,
LocalVersion: up.LocalVersion,
Version: up.RemoteVersion,
})
names.Add(up.Name)
}
for i := range aurUp.Up {
up := &aurUp.Up[i]
// add devel packages if they are not already in the list
if names.Contains(up.Name) {
continue
}
// check if deps are satisfied for aur packages
reason := dep.Explicit
if up.Reason == alpm.PkgReasonDepend {
reason = dep.Dep
}
if filter != nil && !filter(up) {
continue
}
aurPkg := aurdata[up.Name]
graph = u.grapher.GraphAURTarget(ctx, graph, aurPkg, &dep.InstallInfo{
Reason: reason,
Source: dep.AUR,
AURBase: &aurPkg.PackageBase,
Upgrade: true,
Version: up.RemoteVersion,
LocalVersion: up.LocalVersion,
})
}
if u.cfg.Mode.AtLeastRepo() {
u.log.OperationInfoln(gotext.Get("Searching databases for updates..."))
syncUpgrades, err := u.dbExecutor.SyncUpgrades(enableDowngrade)
for _, up := range syncUpgrades {
dbName := up.Package.DB().Name()
if filter != nil && !filter(&db.Upgrade{
Name: up.Package.Name(),
RemoteVersion: up.Package.Version(),
Repository: dbName,
Base: up.Package.Base(),
LocalVersion: up.LocalVersion,
Reason: up.Reason,
}) {
continue
}
reason := dep.Explicit
if up.Reason == alpm.PkgReasonDepend {
reason = dep.Dep
}
graph = u.grapher.GraphSyncPkg(ctx, graph, up.Package, &dep.InstallInfo{
Source: dep.Sync,
Reason: reason,
Version: up.Package.Version(),
SyncDBName: &dbName,
LocalVersion: up.LocalVersion,
Upgrade: true,
})
}
errs.Add(err)
}
return errs.Return()
}
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()}
_ = graph.ForEach(func(name string, info *dep.InstallInfo) error {
alpmReason := alpm.PkgReasonExplicit
if info.Reason == dep.Dep {
alpmReason = alpm.PkgReasonDepend
}
if info.Source == dep.AUR {
aurRepo := "aur"
if info.Devel {
aurRepo = "devel"
}
aurUp.Up = append(aurUp.Up, Upgrade{
Name: name,
RemoteVersion: info.Version,
Repository: aurRepo,
Base: *info.AURBase,
LocalVersion: info.LocalVersion,
Reason: alpmReason,
})
} else if info.Source == dep.Sync {
repoUp.Up = append(repoUp.Up, Upgrade{
Name: name,
RemoteVersion: info.Version,
Repository: *info.SyncDBName,
Base: "",
LocalVersion: info.LocalVersion,
Reason: alpmReason,
})
}
return nil
})
return aurUp, repoUp
}
func (u *UpgradeService) GraphUpgrades(ctx context.Context,
graph *topo.Graph[string, *dep.InstallInfo],
enableDowngrade bool, filter Filter,
) (*topo.Graph[string, *dep.InstallInfo], error) {
if graph == nil {
graph = topo.New[string, *dep.InstallInfo]()
}
err := u.upGraph(ctx, graph, enableDowngrade, filter)
if err != nil {
return graph, err
}
if graph.Len() == 0 {
return graph, nil
}
return graph, nil
}
// userExcludeUpgrades asks the user which packages to exclude from the upgrade and
// removes them from the graph
func (u *UpgradeService) UserExcludeUpgrades(graph *topo.Graph[string, *dep.InstallInfo]) ([]string, error) {
allUpLen := graph.Len()
if allUpLen == 0 {
return []string{}, nil
}
aurUp, repoUp := u.graphToUpSlice(graph)
sort.Sort(repoUp)
sort.Sort(aurUp)
allUp := UpSlice{Up: append(repoUp.Up, aurUp.Up...), Repos: append(repoUp.Repos, aurUp.Repos...)}
u.log.Printf("%s"+text.Bold(" %d ")+"%s\n", text.Bold(text.Cyan("::")), allUpLen, text.Bold(gotext.Get("Packages to upgrade.")))
allUp.Print(u.log)
u.log.Infoln(gotext.Get("Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)"))
u.log.Warnln(gotext.Get("May cause partial upgrades and break systems"))
numbers, err := u.log.GetInput(u.cfg.AnswerUpgrade, settings.NoConfirm)
if err != nil {
return nil, err
}
// upgrade menu asks you which packages to NOT upgrade so in this case
// exclude and include are kind of swapped
exclude, include, otherExclude, otherInclude := intrange.ParseNumberMenu(numbers)
isInclude := len(include) == 0 && len(otherInclude) == 0
excluded := make([]string, 0)
for i := range allUp.Up {
up := &allUp.Up[i]
// choices do not apply to non-installed packages
if up.LocalVersion == "" {
continue
}
if isInclude && otherExclude.Get(up.Repository) {
u.log.Debugln("pruning", up.Name)
excluded = append(excluded, graph.Prune(up.Name)...)
continue
}
if isInclude && exclude.Get(allUpLen-i) {
u.log.Debugln("pruning", up.Name)
excluded = append(excluded, graph.Prune(up.Name)...)
continue
}
if !isInclude && !(include.Get(allUpLen-i) || otherInclude.Get(up.Repository)) {
u.log.Debugln("pruning", up.Name)
excluded = append(excluded, graph.Prune(up.Name)...)
continue
}
}
return excluded, nil
}