feat(new_install): show (#1915)

* show new packages in upgrade form if they exist

* refactor up select

* remove unused graph parts

* readd len

* Complete upgrade graphing

* Extract to upgrade pkg

* remove unused dep method

* remove uneeded dep

* cleanup method

* specify io Reader for testing

* use specified input vector

* fix non-active devel

* test base cases

* add devel test cases

* add range tests

* add logger struct

* use logger struct in upgrade

* follow golangci recommendations

* update deps

* update golangci
This commit is contained in:
Jo 2023-02-17 20:01:26 +01:00 committed by GitHub
parent 4f50b799ef
commit 0bf4c2e502
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 941 additions and 343 deletions

View file

@ -8,5 +8,5 @@ COPY go.mod .
RUN pacman-key --init && pacman -Sy && pacman -S --overwrite=* --noconfirm archlinux-keyring && \
pacman -Su --overwrite=* --needed --noconfirm doxygen meson asciidoc go git gcc make sudo base-devel && \
rm -rfv /var/cache/pacman/* /var/lib/pacman/sync/* && \
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.50.1 && \
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.51.1 && \
go mod download

8
cmd.go
View file

@ -205,16 +205,16 @@ func getFilter(cmdArgs *parser.Arguments) (upgrade.Filter, error) {
case deps && explicit:
return nil, errors.New(gotext.Get("invalid option: '--deps' and '--explicit' may not be used together"))
case deps:
return func(pkg upgrade.Upgrade) bool {
return func(pkg *upgrade.Upgrade) bool {
return pkg.Reason == alpm.PkgReasonDepend
}, nil
case explicit:
return func(pkg upgrade.Upgrade) bool {
return func(pkg *upgrade.Upgrade) bool {
return pkg.Reason == alpm.PkgReasonExplicit
}, nil
}
return func(pkg upgrade.Upgrade) bool {
return func(pkg *upgrade.Upgrade) bool {
return true
}, nil
}
@ -406,7 +406,7 @@ func displayNumberMenu(ctx context.Context, pkgS []string, dbExecutor db.Executo
text.Infoln(gotext.Get("Packages to install (eg: 1 2 3, 1-3 or ^4)"))
numberBuf, err := text.GetInput("", false)
numberBuf, err := text.GetInput(os.Stdin, "", false)
if err != nil {
return err
}

8
go.mod
View file

@ -9,9 +9,9 @@ require (
github.com/bradleyjkemp/cupaloy v2.3.0+incompatible
github.com/leonelquinteros/gotext v1.5.1
github.com/stretchr/testify v1.8.1
golang.org/x/sys v0.3.0
golang.org/x/term v0.3.0
golang.org/x/text v0.5.0 // indirect
golang.org/x/sys v0.5.0
golang.org/x/term v0.5.0
golang.org/x/text v0.7.0 // indirect
gopkg.in/h2non/gock.v1 v1.1.2
)
@ -28,7 +28,7 @@ require (
github.com/deckarep/golang-set/v2 v2.1.0
github.com/itchyny/gojq v0.12.11 // indirect
github.com/itchyny/timefmt-go v0.1.5 // indirect
github.com/ohler55/ojg v1.15.0 // indirect
github.com/ohler55/ojg v1.17.4 // indirect
)
go 1.19

8
go.sum
View file

@ -31,6 +31,8 @@ github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/ohler55/ojg v1.15.0 h1:Z95FvBiMsMOOGP9Nzv5OVV4ND2KnEMxk0GOS8Kvcahg=
github.com/ohler55/ojg v1.15.0/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k=
github.com/ohler55/ojg v1.17.4 h1:6Ss87DyAZHU0ODZu6Cmuahj5UiVaRD1n8C4KNm0qMYg=
github.com/ohler55/ojg v1.17.4/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -62,15 +64,21 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=

View file

@ -72,8 +72,6 @@ func graphPackage(
}
fmt.Fprintln(os.Stdout, graph.String())
fmt.Fprintln(os.Stdout, "\nlayers\n", graph.TopoSortedLayers())
fmt.Fprintln(os.Stdout, "\ninverted order\n", graph.TopoSorted())
fmt.Fprintln(os.Stdout, "\nlayers map\n", graph.TopoSortedLayerMap(nil))
return nil

View file

@ -26,6 +26,12 @@ type Upgrade struct {
Reason alpm.PkgReason
}
type SyncUpgrade struct {
Package alpm.IPackage
LocalVersion string
Reason alpm.PkgReason
}
type Executor interface {
AlpmArchitectures() ([]string, error)
BiggestPackages() []IPackage
@ -45,7 +51,8 @@ type Executor interface {
PackageProvides(IPackage) []Depend
PackagesFromGroup(string) []IPackage
RefreshHandle() error
RepoUpgrades(bool) ([]Upgrade, error)
SyncUpgrades(enableDowngrade bool) (
map[string]SyncUpgrade, error)
Repos() []string
SatisfierFromDB(string, string) IPackage
SyncPackage(string) IPackage

View file

@ -14,7 +14,6 @@ import (
"github.com/Jguer/yay/v11/pkg/db"
"github.com/Jguer/yay/v11/pkg/settings"
"github.com/Jguer/yay/v11/pkg/text"
"github.com/Jguer/yay/v11/pkg/upgrade"
)
type AlpmExecutor struct {
@ -204,7 +203,7 @@ func (ae *AlpmExecutor) questionCallback() func(question alpm.QuestionAny) {
break
}
numberBuf, err := text.GetInput("", false)
numberBuf, err := text.GetInput(os.Stdin, "", false)
if err != nil {
text.Errorln(err)
break
@ -407,18 +406,19 @@ func (ae *AlpmExecutor) PackageGroups(pkg alpm.IPackage) []string {
// upRepo gathers local packages and checks if they have new versions.
// Output: Upgrade type package list.
func (ae *AlpmExecutor) RepoUpgrades(enableDowngrade bool) ([]db.Upgrade, error) {
func (ae *AlpmExecutor) SyncUpgrades(enableDowngrade bool) (
map[string]db.SyncUpgrade, error,
) {
ups := map[string]db.SyncUpgrade{}
var errReturn error
slice := []db.Upgrade{}
localDB, errDB := ae.handle.LocalDB()
if errDB != nil {
return slice, errDB
return ups, errDB
}
if err := ae.handle.TransInit(alpm.TransFlagNoLock); err != nil {
return slice, err
return ups, err
}
defer func() {
@ -426,7 +426,7 @@ func (ae *AlpmExecutor) RepoUpgrades(enableDowngrade bool) ([]db.Upgrade, error)
}()
if err := ae.handle.SyncSysupgrade(enableDowngrade); err != nil {
return slice, err
return ups, err
}
_ = ae.handle.TransGetAdd().ForEach(func(pkg alpm.IPackage) error {
@ -438,18 +438,16 @@ func (ae *AlpmExecutor) RepoUpgrades(enableDowngrade bool) ([]db.Upgrade, error)
reason = localPkg.Reason()
}
slice = append(slice, upgrade.Upgrade{
Name: pkg.Name(),
Base: pkg.Base(),
Repository: pkg.DB().Name(),
LocalVersion: localVer,
RemoteVersion: pkg.Version(),
Reason: reason,
})
ups[pkg.Name()] = db.SyncUpgrade{
Package: pkg,
Reason: reason,
LocalVersion: localVer,
}
return nil
})
return slice, errReturn
return ups, errReturn
}
func (ae *AlpmExecutor) BiggestPackages() []alpm.IPackage {

View file

@ -16,12 +16,30 @@ type (
type DBExecutor struct {
db.Executor
IsCorrectVersionInstalledFn func(string, string) bool
SyncPackageFn func(string) IPackage
PackagesFromGroupFn func(string) []IPackage
LocalSatisfierExistsFn func(string) bool
SyncSatisfierFn func(string) IPackage
AlpmArchitecturesFn func() ([]string, error)
IsCorrectVersionInstalledFn func(string, string) bool
SyncPackageFn func(string) IPackage
PackagesFromGroupFn func(string) []IPackage
LocalSatisfierExistsFn func(string) bool
SyncSatisfierFn func(string) IPackage
AlpmArchitecturesFn func() ([]string, error)
InstalledRemotePackageNamesFn func() []string
InstalledRemotePackagesFn func() map[string]IPackage
SyncUpgradesFn func(bool) (map[string]db.SyncUpgrade, error)
ReposFn func() []string
}
func (t *DBExecutor) InstalledRemotePackageNames() []string {
if t.InstalledRemotePackageNamesFn != nil {
return t.InstalledRemotePackageNamesFn()
}
panic("implement me")
}
func (t *DBExecutor) InstalledRemotePackages() map[string]IPackage {
if t.InstalledRemotePackagesFn != nil {
return t.InstalledRemotePackagesFn()
}
panic("implement me")
}
func (t *DBExecutor) AlpmArchitectures() ([]string, error) {
@ -93,40 +111,46 @@ func (t *DBExecutor) PackagesFromGroup(s string) []IPackage {
panic("implement me")
}
func (t DBExecutor) RefreshHandle() error {
func (t *DBExecutor) RefreshHandle() error {
panic("implement me")
}
func (t DBExecutor) RepoUpgrades(b bool) ([]Upgrade, error) {
func (t *DBExecutor) SyncUpgrades(b bool) (map[string]db.SyncUpgrade, error) {
if t.SyncUpgradesFn != nil {
return t.SyncUpgradesFn(b)
}
panic("implement me")
}
func (t DBExecutor) Repos() []string {
func (t *DBExecutor) Repos() []string {
if t.ReposFn != nil {
return t.ReposFn()
}
panic("implement me")
}
func (t DBExecutor) SatisfierFromDB(s, s2 string) IPackage {
func (t *DBExecutor) SatisfierFromDB(s, s2 string) IPackage {
panic("implement me")
}
func (t DBExecutor) SyncPackage(s string) IPackage {
func (t *DBExecutor) SyncPackage(s string) IPackage {
if t.SyncPackageFn != nil {
return t.SyncPackageFn(s)
}
panic("implement me")
}
func (t DBExecutor) SyncPackages(s ...string) []IPackage {
func (t *DBExecutor) SyncPackages(s ...string) []IPackage {
panic("implement me")
}
func (t DBExecutor) SyncSatisfier(s string) IPackage {
func (t *DBExecutor) SyncSatisfier(s string) IPackage {
if t.SyncSatisfierFn != nil {
return t.SyncSatisfierFn(s)
}
panic("implement me")
}
func (t DBExecutor) SyncSatisfierExists(s string) bool {
func (t *DBExecutor) SyncSatisfierExists(s string) bool {
panic("implement me")
}

View file

@ -516,7 +516,7 @@ func providerMenu(dep string, providers providers, noConfirm bool) *query.Pkg {
return providers.Pkgs[0]
}
numberBuf, err := text.GetInput("", false)
numberBuf, err := text.GetInput(os.Stdin, "", false)
if err != nil {
fmt.Fprintln(os.Stderr, err)

View file

@ -21,12 +21,16 @@ import (
)
type InstallInfo struct {
Source Source
Reason Reason
Version string
SrcinfoPath *string
AURBase *string
SyncDBName *string
Source Source
Reason Reason
Version string
LocalVersion string
SrcinfoPath *string
AURBase *string
SyncDBName *string
Upgrade bool
Devel bool
}
func (i *InstallInfo) String() string {
@ -150,6 +154,13 @@ func (g *Grapher) GraphFromTargets(ctx context.Context,
},
})
g.GraphSyncPkg(ctx, graph, pkg, &InstallInfo{
Source: Sync,
Reason: Explicit,
Version: pkg.Version(),
SyncDBName: &dbName,
})
continue
}
@ -205,7 +216,7 @@ func (g *Grapher) pickSrcInfoPkgs(pkgs []aurc.Pkg) ([]aurc.Pkg, error) {
}
text.Infoln(gotext.Get("Packages to exclude") + " (eg: \"1 2 3\", \"1-3\", \"^4\"):")
numberBuf, err := text.GetInput("", g.noConfirm)
numberBuf, err := text.GetInput(os.Stdin, "", g.noConfirm)
if err != nil {
return nil, err
}
@ -284,6 +295,44 @@ func (g *Grapher) addDepNodes(ctx context.Context, pkg *aur.Pkg, graph *topo.Gra
}
}
func (g *Grapher) GraphSyncPkg(ctx context.Context,
graph *topo.Graph[string, *InstallInfo],
pkg alpm.IPackage, instalInfo *InstallInfo,
) *topo.Graph[string, *InstallInfo] {
if graph == nil {
graph = topo.New[string, *InstallInfo]()
}
graph.AddNode(pkg.Name())
g.ValidateAndSetNodeInfo(graph, pkg.Name(), &topo.NodeInfo[*InstallInfo]{
Color: colorMap[Explicit],
Background: bgColorMap[Sync],
Value: instalInfo,
})
return graph
}
func (g *Grapher) GraphAURTarget(ctx context.Context,
graph *topo.Graph[string, *InstallInfo],
pkg *aurc.Pkg, instalInfo *InstallInfo,
) *topo.Graph[string, *InstallInfo] {
if graph == nil {
graph = topo.New[string, *InstallInfo]()
}
graph.AddNode(pkg.Name)
g.ValidateAndSetNodeInfo(graph, pkg.Name, &topo.NodeInfo[*InstallInfo]{
Color: colorMap[Explicit],
Background: bgColorMap[AUR],
Value: instalInfo,
})
g.addDepNodes(ctx, pkg, graph)
return graph
}
func (g *Grapher) GraphFromAURCache(ctx context.Context,
graph *topo.Graph[string, *InstallInfo],
targets []string,
@ -302,19 +351,12 @@ func (g *Grapher) GraphFromAURCache(ctx context.Context,
pkg := provideMenu(g.w, target, aurPkgs, g.noConfirm)
graph.AddNode(pkg.Name)
g.ValidateAndSetNodeInfo(graph, pkg.Name, &topo.NodeInfo[*InstallInfo]{
Color: colorMap[Explicit],
Background: bgColorMap[AUR],
Value: &InstallInfo{
Source: AUR,
Reason: Explicit,
AURBase: &pkg.PackageBase,
Version: pkg.Version,
},
graph = g.GraphAURTarget(ctx, graph, pkg, &InstallInfo{
AURBase: &pkg.PackageBase,
Reason: Explicit,
Source: AUR,
Version: pkg.Version,
})
g.addDepNodes(ctx, pkg, graph)
}
return graph, nil
@ -486,7 +528,7 @@ func provideMenu(w io.Writer, dep string, options []aur.Pkg, noConfirm bool) *au
return &options[0]
}
numberBuf, err := text.GetInput("", false)
numberBuf, err := text.GetInput(os.Stdin, "", false)
if err != nil {
fmt.Fprintln(os.Stderr, err)
@ -576,28 +618,3 @@ func archStringToString(alpmArches []string, archString []gosrc.ArchString) []st
return pkgs
}
func AddUpgradeToGraph(pkg *db.Upgrade, graph *topo.Graph[string, *InstallInfo]) {
source := Sync
if pkg.Repository == "aur" || pkg.Repository == "devel" {
source = AUR
}
reason := Explicit
if pkg.Reason == alpm.PkgReasonDepend {
reason = Dep
}
graph.AddNode(pkg.Name)
graph.SetNodeInfo(pkg.Name, &topo.NodeInfo[*InstallInfo]{
Color: colorMap[reason],
Background: bgColorMap[source],
Value: &InstallInfo{
Source: source,
Reason: reason,
Version: pkg.RemoteVersion,
AURBase: &pkg.Base,
SyncDBName: &pkg.Repository,
},
})
}

View file

@ -61,7 +61,7 @@ func editor(editorConfig, editorFlags string, noConfirm bool) (editor string, ar
for {
text.Infoln(gotext.Get("Edit PKGBUILD with?"))
editorInput, err := text.GetInput("", noConfirm)
editorInput, err := text.GetInput(os.Stdin, "", noConfirm)
if err != nil {
fmt.Fprintln(os.Stderr, err)
continue

View file

@ -46,7 +46,7 @@ func selectionMenu(w io.Writer, pkgbuildDirs map[string]string, bases []string,
text.Infoln(message)
text.Infoln(gotext.Get("%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)", text.Cyan(gotext.Get("[N]one"))))
selectInput, err := text.GetInput(defaultAnswer, noConfirm)
selectInput, err := text.GetInput(os.Stdin, defaultAnswer, noConfirm)
if err != nil {
return nil, err
}

View file

@ -41,9 +41,7 @@ func AURInfo(ctx context.Context, aurClient aur.ClientInterface, names []string,
}
mux.Lock()
for i := range tempInfo {
info = append(info, tempInfo[i])
}
info = append(info, tempInfo...)
mux.Unlock()
}

View file

@ -215,9 +215,7 @@ func queryAUR(ctx context.Context,
Contains: true,
})
for i := range q {
r = append(r, q[i])
}
r = append(r, q...)
if errM == nil {
return r, nil

View file

@ -295,6 +295,7 @@ func NewConfig(version string) (*Configuration, error) {
AURClient: nil,
VoteClient: voteClient,
QueryBuilder: nil,
Logger: text.NewLogger(os.Stdout, os.Stdin, newConfig.Debug, "runtime"),
}
var errAURCache error

View file

@ -8,6 +8,7 @@ import (
"github.com/Jguer/yay/v11/pkg/query"
"github.com/Jguer/yay/v11/pkg/settings/exe"
"github.com/Jguer/yay/v11/pkg/settings/parser"
"github.com/Jguer/yay/v11/pkg/text"
"github.com/Jguer/yay/v11/pkg/vcs"
"github.com/Jguer/aur"
@ -35,4 +36,5 @@ type Runtime struct {
VoteClient *vote.Client
AURCache AURCache
DBExecutor db.Executor
Logger *text.Logger
}

View file

@ -3,10 +3,10 @@ package text
import (
"bufio"
"fmt"
"os"
"io"
)
func GetInput(defaultValue string, noConfirm bool) (string, error) {
func (l *Logger) GetInput(defaultValue string, noConfirm bool) (string, error) {
Info()
if defaultValue != "" || noConfirm {
@ -14,7 +14,7 @@ func GetInput(defaultValue string, noConfirm bool) (string, error) {
return defaultValue, nil
}
reader := bufio.NewReader(os.Stdin)
reader := bufio.NewReader(l.r)
buf, overflow, err := reader.ReadLine()
if err != nil {
@ -27,3 +27,7 @@ func GetInput(defaultValue string, noConfirm bool) (string, error) {
return string(buf), nil
}
func GetInput(r io.Reader, defaultValue string, noConfirm bool) (string, error) {
return globalLogger.GetInput(defaultValue, noConfirm)
}

View file

@ -21,61 +21,55 @@ const (
var (
cachedColumnCount = -1
DebugMode = false
globalLogger = NewLogger(os.Stdout, os.Stdin, DebugMode, "global")
)
func Debugln(a ...interface{}) {
if !DebugMode {
return
}
fmt.Fprintln(os.Stdout, append([]interface{}{Bold(yellow("[DEBUG]"))}, a...)...)
fmt.Fprint(os.Stdout, ResetCode)
globalLogger.Debugln(a...)
}
func OperationInfoln(a ...interface{}) {
fmt.Fprint(os.Stdout, append([]interface{}{Bold(Cyan(opSymbol + " ")), boldCode}, a...)...)
fmt.Fprintln(os.Stdout, ResetCode)
globalLogger.OperationInfoln(a...)
}
func OperationInfo(a ...interface{}) {
fmt.Fprint(os.Stdout, append([]interface{}{Bold(Cyan(opSymbol + " ")), boldCode}, a...)...)
fmt.Fprint(os.Stdout, ResetCode)
globalLogger.OperationInfo(a...)
}
func SprintOperationInfo(a ...interface{}) string {
return fmt.Sprint(append([]interface{}{Bold(Cyan(opSymbol + " ")), boldCode}, a...)...) + ResetCode
return globalLogger.SprintOperationInfo(a...)
}
func Info(a ...interface{}) {
fmt.Fprint(os.Stdout, append([]interface{}{Bold(Green(arrow + " "))}, a...)...)
globalLogger.Info(a...)
}
func Infoln(a ...interface{}) {
fmt.Fprintln(os.Stdout, append([]interface{}{Bold(Green(arrow))}, a...)...)
globalLogger.Infoln(a...)
}
func SprintWarn(a ...interface{}) string {
return fmt.Sprint(append([]interface{}{Bold(yellow(smallArrow + " "))}, a...)...)
return globalLogger.SprintWarn(a...)
}
func Warn(a ...interface{}) {
fmt.Fprint(os.Stdout, append([]interface{}{Bold(yellow(smallArrow + " "))}, a...)...)
globalLogger.Warn(a...)
}
func Warnln(a ...interface{}) {
fmt.Fprintln(os.Stdout, append([]interface{}{Bold(yellow(smallArrow))}, a...)...)
globalLogger.Warnln(a...)
}
func SprintError(a ...interface{}) string {
return fmt.Sprint(append([]interface{}{Bold(Red(smallArrow + " "))}, a...)...)
return globalLogger.SprintError(a...)
}
func Error(a ...interface{}) {
fmt.Fprint(os.Stderr, append([]interface{}{Bold(Red(smallArrow + " "))}, a...)...)
globalLogger.Error(a...)
}
func Errorln(a ...interface{}) {
fmt.Fprintln(os.Stderr, append([]interface{}{Bold(Red(smallArrow))}, a...)...)
globalLogger.Errorln(a...)
}
func getColumnCount() int {

84
pkg/text/service.go Normal file
View file

@ -0,0 +1,84 @@
package text
import (
"fmt"
"io"
)
type Logger struct {
name string
debug bool
w io.Writer
r io.Reader
}
func NewLogger(w io.Writer, r io.Reader, debug bool, name string) *Logger {
return &Logger{
w: w,
name: name,
debug: debug,
r: r,
}
}
func (l *Logger) Child(name string) *Logger {
return NewLogger(l.w, l.r, l.debug, name)
}
func (l *Logger) Debugln(a ...any) {
if !DebugMode {
return
}
fmt.Fprintln(l.w, append([]interface{}{
Bold(yellow(fmt.Sprintf("[DEBUG:%s]", l.name))),
}, a...)...)
}
func (l *Logger) OperationInfoln(a ...any) {
fmt.Fprintln(l.w, l.SprintOperationInfo(a...))
}
func (l *Logger) OperationInfo(a ...any) {
fmt.Fprint(l.w, l.SprintOperationInfo(a...))
}
func (l *Logger) SprintOperationInfo(a ...any) string {
return fmt.Sprint(append([]interface{}{Bold(Cyan(opSymbol + " ")), boldCode}, a...)...) + ResetCode
}
func (l *Logger) Info(a ...any) {
fmt.Fprint(l.w, append([]interface{}{Bold(Green(arrow + " "))}, a...)...)
}
func (l *Logger) Infoln(a ...any) {
fmt.Fprintln(l.w, append([]interface{}{Bold(Green(arrow))}, a...)...)
}
func (l *Logger) Warn(a ...any) {
fmt.Fprint(l.w, l.SprintWarn(a...))
}
func (l *Logger) Warnln(a ...any) {
fmt.Fprintln(l.w, l.SprintWarn(a...))
}
func (l *Logger) SprintWarn(a ...any) string {
return fmt.Sprint(append([]interface{}{Bold(yellow(smallArrow + " "))}, a...)...)
}
func (l *Logger) Error(a ...any) {
fmt.Fprint(l.w, l.SprintError(a...))
}
func (l *Logger) Errorln(a ...any) {
fmt.Fprintln(l.w, l.SprintError(a...))
}
func (l *Logger) SprintError(a ...any) string {
return fmt.Sprint(append([]interface{}{Bold(Red(smallArrow + " "))}, a...)...)
}
func (l *Logger) Printf(format string, a ...any) {
fmt.Fprintf(l.w, format, a...)
}

View file

@ -3,12 +3,13 @@ package topo
import (
"fmt"
"strings"
"github.com/Jguer/yay/v11/pkg/text"
)
type (
AliasMap[T comparable] map[T]T
NodeSet[T comparable] map[T]bool
DepMap[T comparable] map[T]NodeSet[T]
NodeSet[T comparable] map[T]bool
DepMap[T comparable] map[T]NodeSet[T]
)
type NodeInfo[V any] struct {
@ -20,9 +21,7 @@ type NodeInfo[V any] struct {
type CheckFn[T comparable, V any] func(T, V) error
type Graph[T comparable, V any] struct {
alias AliasMap[T] // alias -> aliased
aliases DepMap[T] // aliased -> alias
nodes NodeSet[T]
nodes NodeSet[T]
// node info map
nodeInfo map[T]*NodeInfo[V]
@ -31,7 +30,6 @@ type Graph[T comparable, V any] struct {
dependencies DepMap[T]
// `dependents` tracks parent -> children.
dependents DepMap[T]
// Keep track of the nodes of the graph themselves.
}
func New[T comparable, V any]() *Graph[T, V] {
@ -39,8 +37,6 @@ func New[T comparable, V any]() *Graph[T, V] {
nodes: make(NodeSet[T]),
dependencies: make(DepMap[T]),
dependents: make(DepMap[T]),
alias: make(AliasMap[T]),
aliases: make(DepMap[T]),
nodeInfo: make(map[T]*NodeInfo[V]),
}
}
@ -50,71 +46,34 @@ func (g *Graph[T, V]) Len() int {
}
func (g *Graph[T, V]) Exists(node T) bool {
// check aliases
node = g.getAlias(node)
_, ok := g.nodes[node]
return ok
}
func (g *Graph[T, V]) Alias(node, alias T) error {
if alias == node {
return nil
}
// add node
func (g *Graph[T, V]) AddNode(node T) {
g.nodes[node] = true
}
// add alias
if _, ok := g.alias[alias]; ok {
return ErrConflictingAlias
func (g *Graph[T, V]) ForEach(f CheckFn[T, V]) error {
for node := range g.nodes {
if err := f(node, g.nodeInfo[node].Value); err != nil {
return err
}
}
g.alias[alias] = node
g.aliases.addNodeToNodeset(node, alias)
return nil
}
func (g *Graph[T, V]) AddNode(node T) {
node = g.getAlias(node)
g.nodes[node] = true
}
func (g *Graph[T, V]) getAlias(node T) T {
if aliasNode, ok := g.alias[node]; ok {
return aliasNode
}
return node
}
func (g *Graph[T, V]) SetNodeInfo(node T, nodeInfo *NodeInfo[V]) {
g.nodeInfo[g.getAlias(node)] = nodeInfo
g.nodeInfo[node] = nodeInfo
}
func (g *Graph[T, V]) GetNodeInfo(node T) *NodeInfo[V] {
return g.nodeInfo[g.getAlias(node)]
}
// Retrieve aliases of a node.
func (g *Graph[T, V]) GetAliases(node T) []T {
size := len(g.aliases[node])
aliases := make([]T, 0, size)
for alias := range g.aliases[node] {
aliases = append(aliases, alias)
}
return aliases
return g.nodeInfo[node]
}
func (g *Graph[T, V]) DependOn(child, parent T) error {
child = g.getAlias(child)
parent = g.getAlias(parent)
if child == parent {
return ErrSelfReferential
}
@ -182,20 +141,8 @@ func (g *Graph[T, V]) HasDependent(parent, child T) bool {
return ok
}
func (g *Graph[T, V]) Leaves() []T {
leaves := make([]T, 0)
for node := range g.nodes {
if _, ok := g.dependencies[node]; !ok {
leaves = append(leaves, node)
}
}
return leaves
}
// LeavesMap returns a map of leaves with the node as key and the node info value as value.
func (g *Graph[T, V]) LeavesMap() map[T]V {
// leavesMap returns a map of leaves with the node as key and the node info value as value.
func (g *Graph[T, V]) leavesMap() map[T]V {
leaves := make(map[T]V, 0)
for node := range g.nodes {
@ -212,29 +159,6 @@ func (g *Graph[T, V]) LeavesMap() map[T]V {
return leaves
}
// TopoSortedLayers returns a slice of all of the graph nodes in topological sort order.
func (g *Graph[T, V]) TopoSortedLayers() [][]T {
layers := [][]T{}
// Copy the graph
shrinkingGraph := g.clone()
for {
leaves := shrinkingGraph.Leaves()
if len(leaves) == 0 {
break
}
layers = append(layers, leaves)
for _, leafNode := range leaves {
shrinkingGraph.remove(leafNode)
}
}
return layers
}
// TopoSortedLayerMap returns a slice of all of the graph nodes in topological sort order with their node info.
func (g *Graph[T, V]) TopoSortedLayerMap(checkFn CheckFn[T, V]) []map[T]V {
layers := []map[T]V{}
@ -243,7 +167,7 @@ func (g *Graph[T, V]) TopoSortedLayerMap(checkFn CheckFn[T, V]) []map[T]V {
shrinkingGraph := g.clone()
for {
leaves := shrinkingGraph.LeavesMap()
leaves := shrinkingGraph.leavesMap()
if len(leaves) == 0 {
break
}
@ -263,17 +187,50 @@ func (g *Graph[T, V]) TopoSortedLayerMap(checkFn CheckFn[T, V]) []map[T]V {
return layers
}
func (dm DepMap[T]) removeFromDepmap(key, node T) {
// returns if it was the last
func (dm DepMap[T]) removeFromDepmap(key, node T) bool {
if nodes := dm[key]; len(nodes) == 1 {
// The only element in the nodeset must be `node`, so we
// can delete the entry entirely.
delete(dm, key)
return true
} else {
// Otherwise, remove the single node from the nodeset.
delete(nodes, node)
return false
}
}
// Prune removes the node,
// its dependencies if there are no other dependents
// and its dependents
func (g *Graph[T, V]) Prune(node T) {
// Remove edges from things that depend on `node`.
for dependent := range g.dependents[node] {
last := g.dependencies.removeFromDepmap(dependent, node)
text.Debugln("pruning dependent", dependent, last)
if last {
g.Prune(dependent)
}
}
delete(g.dependents, node)
// Remove all edges from node to the things it depends on.
for dependency := range g.dependencies[node] {
last := g.dependents.removeFromDepmap(dependency, node)
text.Debugln("pruning dependency", dependency, last)
if last {
g.Prune(dependency)
}
}
delete(g.dependencies, node)
// Finally, remove the node itself.
delete(g.nodes, node)
}
func (g *Graph[T, V]) remove(node T) {
// Remove edges from things that depend on `node`.
for dependent := range g.dependents[node] {
@ -293,24 +250,6 @@ func (g *Graph[T, V]) remove(node T) {
delete(g.nodes, node)
}
// TopoSorted returns all the nodes in the graph is topological sort order.
func (g *Graph[T, V]) TopoSorted() []T {
nodeCount := 0
layers := g.TopoSortedLayers()
for _, layer := range layers {
nodeCount += len(layer)
}
allNodes := make([]T, 0, nodeCount)
for _, layer := range layers {
allNodes = append(allNodes, layer...)
}
return allNodes
}
func (g *Graph[T, V]) Dependencies(child T) NodeSet[T] {
return g.buildTransitive(child, g.immediateDependencies)
}

303
pkg/upgrade/service.go Normal file
View file

@ -0,0 +1,303 @@
package upgrade
import (
"context"
"sort"
"github.com/Jguer/aur"
"github.com/Jguer/aur/metadata"
"github.com/Jguer/go-alpm/v2"
mapset "github.com/deckarep/golang-set/v2"
"github.com/leonelquinteros/gotext"
"github.com/Jguer/yay/v11/pkg/db"
"github.com/Jguer/yay/v11/pkg/dep"
"github.com/Jguer/yay/v11/pkg/intrange"
"github.com/Jguer/yay/v11/pkg/multierror"
"github.com/Jguer/yay/v11/pkg/query"
"github.com/Jguer/yay/v11/pkg/settings"
"github.com/Jguer/yay/v11/pkg/text"
"github.com/Jguer/yay/v11/pkg/topo"
"github.com/Jguer/yay/v11/pkg/vcs"
)
type UpgradeService struct {
grapher *dep.Grapher
aurCache settings.AURCache
aurClient aur.ClientInterface
dbExecutor db.Executor
vcsStore vcs.Store
runtime *settings.Runtime
cfg *settings.Configuration
log *text.Logger
noConfirm bool
}
func NewUpgradeService(grapher *dep.Grapher, aurCache settings.AURCache,
aurClient aur.ClientInterface, dbExecutor db.Executor,
vcsStore vcs.Store, runtime *settings.Runtime, cfg *settings.Configuration,
noConfirm bool, logger *text.Logger,
) *UpgradeService {
return &UpgradeService{
grapher: grapher,
aurCache: aurCache,
aurClient: aurClient,
dbExecutor: dbExecutor,
vcsStore: vcsStore,
runtime: runtime,
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],
warnings *query.AURWarnings, 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.runtime.Mode.AtLeastAUR() {
u.log.OperationInfoln(gotext.Get("Searching AUR for updates..."))
var _aurdata []aur.Pkg
if u.aurCache != nil {
_aurdata, err = u.aurCache.Get(ctx, &metadata.AURQuery{Needles: remoteNames, By: aur.Name})
} else {
_aurdata, err = query.AURInfo(ctx, u.aurClient, remoteNames, warnings, u.cfg.RequestSplitN)
}
errs.Add(err)
if err == nil {
for i := range _aurdata {
pkg := &_aurdata[i]
aurdata[pkg.Name] = pkg
}
aurUp = UpAUR(remote, aurdata, u.cfg.TimeUpdate)
}
if u.cfg.Devel {
u.log.OperationInfoln(gotext.Get("Checking development packages..."))
develUp = UpDevel(ctx, 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,
})
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.Runtime.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,
) (*topo.Graph[string, *dep.InstallInfo], error) {
if graph == nil {
graph = topo.New[string, *dep.InstallInfo]()
}
warnings := query.NewWarnings()
err := u.upGraph(ctx, graph, warnings, enableDowngrade,
func(*Upgrade) bool { return true })
if err != nil {
return graph, err
}
warnings.Print()
if graph.Len() == 0 {
return graph, nil
}
errUp := u.userExcludeUpgrades(graph)
return graph, errUp
}
// 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]) error {
allUpLen := graph.Len()
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 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
for i := range allUp.Up {
up := &allUp.Up[i]
if isInclude && otherExclude.Get(up.Repository) {
u.log.Debugln("pruning", up.Name)
graph.Prune(up.Name)
}
if isInclude && exclude.Get(allUpLen-i) {
u.log.Debugln("pruning", up.Name)
graph.Prune(up.Name)
continue
}
if !isInclude && !(include.Get(allUpLen-i) || otherInclude.Get(up.Repository)) {
u.log.Debugln("pruning", up.Name)
graph.Prune(up.Name)
continue
}
}
return nil
}

287
pkg/upgrade/service_test.go Normal file
View file

@ -0,0 +1,287 @@
package upgrade
import (
"context"
"io"
"strings"
"testing"
"github.com/Jguer/aur"
"github.com/Jguer/aur/metadata"
"github.com/Jguer/go-alpm/v2"
"github.com/stretchr/testify/assert"
"github.com/Jguer/yay/v11/pkg/db"
"github.com/Jguer/yay/v11/pkg/db/mock"
"github.com/Jguer/yay/v11/pkg/dep"
"github.com/Jguer/yay/v11/pkg/settings"
"github.com/Jguer/yay/v11/pkg/settings/parser"
"github.com/Jguer/yay/v11/pkg/text"
"github.com/Jguer/yay/v11/pkg/topo"
"github.com/Jguer/yay/v11/pkg/vcs"
mockaur "github.com/Jguer/yay/v11/pkg/dep/mock"
)
func ptrString(s string) *string {
return &s
}
func TestUpgradeService_GraphUpgrades(t *testing.T) {
linuxDepInfo := &dep.InstallInfo{
Reason: dep.Explicit,
Source: dep.Sync,
AURBase: nil,
LocalVersion: "4.5.0-1",
Version: "5.0.0-1",
SyncDBName: ptrString("core"),
Upgrade: true,
Devel: false,
}
exampleDepInfoDevel := &dep.InstallInfo{
Source: dep.AUR,
Reason: dep.Dep,
AURBase: ptrString("example"),
LocalVersion: "2.2.1.r32.41baa362-1",
Version: "",
Upgrade: true,
Devel: true,
}
exampleDepInfoAUR := &dep.InstallInfo{
Source: dep.AUR,
Reason: dep.Dep,
AURBase: ptrString("example"),
LocalVersion: "2.2.1.r32.41baa362-1",
Version: "2.2.1.r69.g8a10460-1",
Upgrade: true,
Devel: false,
}
yayDepInfo := &dep.InstallInfo{
Reason: dep.Explicit,
Source: dep.AUR,
AURBase: ptrString("yay"),
LocalVersion: "10.2.3",
Version: "10.2.4",
Upgrade: true,
Devel: false,
}
dbExe := &mock.DBExecutor{
InstalledRemotePackageNamesFn: func() []string {
return []string{"yay", "example-git"}
},
InstalledRemotePackagesFn: func() map[string]mock.IPackage {
mapRemote := make(map[string]mock.IPackage)
mapRemote["yay"] = &mock.Package{
PName: "yay",
PBase: "yay",
PVersion: "10.2.3",
PReason: alpm.PkgReasonExplicit,
}
mapRemote["example-git"] = &mock.Package{
PName: "example-git",
PBase: "example",
PVersion: "2.2.1.r32.41baa362-1",
PReason: alpm.PkgReasonDepend,
}
return mapRemote
},
SyncUpgradesFn: func(bool) (map[string]db.SyncUpgrade, error) {
mapUpgrades := make(map[string]db.SyncUpgrade)
coreDB := mock.NewDB("core")
mapUpgrades["linux"] = db.SyncUpgrade{
Package: &mock.Package{
PName: "linux",
PVersion: "5.0.0-1",
PReason: alpm.PkgReasonDepend,
PDB: coreDB,
},
LocalVersion: "4.5.0-1",
Reason: alpm.PkgReasonExplicit,
}
return mapUpgrades, nil
},
ReposFn: func() []string { return []string{"core"} },
}
vcsStore := &vcs.Mock{
ToUpgradeReturn: []string{"example-git"},
}
mockAUR := &mockaur.MockAUR{
GetFn: func(ctx context.Context, query *metadata.AURQuery) ([]aur.Pkg, error) {
return []aur.Pkg{
{Name: "yay", Version: "10.2.4", PackageBase: "yay"},
{Name: "example-git", Version: "2.2.1.r69.g8a10460-1", PackageBase: "example"},
}, nil
},
}
grapher := dep.NewGrapher(dbExe, mockAUR,
false, true, io.Discard, false, false)
cfg := &settings.Configuration{
Runtime: &settings.Runtime{Mode: parser.ModeAny},
}
type fields struct {
input io.Reader
output io.Writer
noConfirm bool
devel bool
}
type args struct {
graph *topo.Graph[string, *dep.InstallInfo]
enableDowngrade bool
}
tests := []struct {
name string
fields fields
args args
mustExist map[string]*dep.InstallInfo
mustNotExist map[string]bool
wantErr bool
}{
{
name: "no input",
fields: fields{
input: strings.NewReader("\n"),
output: io.Discard,
noConfirm: false,
},
args: args{
graph: nil,
enableDowngrade: false,
},
mustExist: map[string]*dep.InstallInfo{
"yay": yayDepInfo,
"linux": linuxDepInfo,
"example-git": exampleDepInfoAUR,
},
mustNotExist: map[string]bool{},
wantErr: false,
},
{
name: "no input devel",
fields: fields{
input: strings.NewReader("\n"),
output: io.Discard,
noConfirm: false,
devel: true,
},
args: args{
graph: nil,
enableDowngrade: false,
},
mustExist: map[string]*dep.InstallInfo{
"yay": yayDepInfo,
"linux": linuxDepInfo,
"example-git": exampleDepInfoDevel,
},
mustNotExist: map[string]bool{},
wantErr: false,
},
{
name: "exclude yay",
fields: fields{
input: strings.NewReader("1\n"),
output: io.Discard,
noConfirm: false,
},
args: args{
graph: nil,
enableDowngrade: false,
},
mustExist: map[string]*dep.InstallInfo{
"linux": linuxDepInfo,
"example-git": exampleDepInfoAUR,
},
mustNotExist: map[string]bool{"yay": true},
wantErr: false,
},
{
name: "exclude linux",
fields: fields{
input: strings.NewReader("3\n"),
output: io.Discard,
noConfirm: false,
},
args: args{
graph: nil,
enableDowngrade: false,
},
mustExist: map[string]*dep.InstallInfo{
"yay": yayDepInfo,
"example-git": exampleDepInfoAUR,
},
mustNotExist: map[string]bool{"linux": true},
wantErr: false,
},
{
name: "only linux",
fields: fields{
input: strings.NewReader("^3\n"),
output: io.Discard,
noConfirm: false,
},
args: args{
graph: nil,
enableDowngrade: false,
},
mustExist: map[string]*dep.InstallInfo{
"linux": linuxDepInfo,
},
mustNotExist: map[string]bool{"yay": true, "example-git": true},
wantErr: false,
},
{
name: "exclude all",
fields: fields{
input: strings.NewReader("1-3\n"),
output: io.Discard,
noConfirm: false,
},
args: args{
graph: nil,
enableDowngrade: false,
},
mustExist: map[string]*dep.InstallInfo{},
mustNotExist: map[string]bool{"yay": true, "example-git": true, "linux": true},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg.Devel = tt.fields.devel
u := &UpgradeService{
log: text.NewLogger(tt.fields.output, tt.fields.input, true, "test"),
grapher: grapher,
aurCache: mockAUR,
dbExecutor: dbExe,
vcsStore: vcsStore,
runtime: cfg.Runtime,
cfg: cfg,
noConfirm: tt.fields.noConfirm,
}
got, err := u.GraphUpgrades(context.Background(), tt.args.graph, tt.args.enableDowngrade)
if (err != nil) != tt.wantErr {
t.Errorf("UpgradeService.GraphUpgrades() error = %v, wantErr %v", err, tt.wantErr)
return
}
for node, info := range tt.mustExist {
assert.True(t, got.Exists(node), node)
assert.Equal(t, info, got.GetNodeInfo(node).Value)
}
for node := range tt.mustNotExist {
assert.False(t, got.Exists(node), node)
}
})
}
}

View file

@ -10,7 +10,7 @@ import (
)
// Filter decides if specific package should be included in theincluded in the results.
type Filter func(Upgrade) bool
type Filter func(*Upgrade) bool
// Upgrade type describes a system upgrade.
type Upgrade = db.Upgrade
@ -100,7 +100,7 @@ func GetVersionDiff(oldVersion, newVersion string) (left, right string) {
}
// Print prints the details of the packages to upgrade.
func (u UpSlice) Print() {
func (u UpSlice) Print(logger *text.Logger) {
longestName, longestVersion := 0, 0
for k := range u.Up {
@ -120,10 +120,10 @@ func (u UpSlice) Print() {
upgrade := &u.Up[k]
left, right := GetVersionDiff(upgrade.LocalVersion, upgrade.RemoteVersion)
fmt.Print(text.Magenta(fmt.Sprintf(numberPadding, len(u.Up)-k)))
logger.Printf(text.Magenta(fmt.Sprintf(numberPadding, len(u.Up)-k)))
fmt.Printf(namePadding, StylizedNameWithRepository(upgrade))
logger.Printf(namePadding, StylizedNameWithRepository(upgrade))
fmt.Printf("%s -> %s\n", fmt.Sprintf(versionPadding, left), right)
logger.Printf("%s -> %s\n", fmt.Sprintf(versionPadding, left), right)
}
}

View file

@ -100,7 +100,7 @@ func printNumberOfUpdates(ctx context.Context, dbExecutor db.Executor, enableDow
warnings := query.NewWarnings()
old := os.Stdout // keep backup of the real stdout
os.Stdout = nil
aurUp, repoUp, err := upList(ctx, nil, warnings, dbExecutor, enableDowngrade, filter)
aurUp, repoUp, err := upList(ctx, warnings, dbExecutor, enableDowngrade, filter)
os.Stdout = old // restoring the real stdout
if err != nil {
@ -123,7 +123,7 @@ func printUpdateList(ctx context.Context, cmdArgs *parser.Arguments,
remoteNames := dbExecutor.InstalledRemotePackageNames()
localNames := dbExecutor.InstalledSyncPackageNames()
aurUp, repoUp, err := upList(ctx, nil, warnings, dbExecutor, enableDowngrade, filter)
aurUp, repoUp, err := upList(ctx, warnings, dbExecutor, enableDowngrade, filter)
os.Stdout = old // restoring the real stdout
if err != nil {

View file

@ -15,6 +15,7 @@ import (
"github.com/Jguer/yay/v11/pkg/settings/parser"
"github.com/Jguer/yay/v11/pkg/srcinfo"
"github.com/Jguer/yay/v11/pkg/text"
"github.com/Jguer/yay/v11/pkg/upgrade"
"github.com/leonelquinteros/gotext"
)
@ -54,7 +55,11 @@ func syncInstall(ctx context.Context,
if cmdArgs.ExistsArg("u", "sysupgrade") {
var errSysUp error
graph, _, errSysUp = sysupgradeTargetsV2(ctx, aurCache, dbExecutor, graph, cmdArgs.ExistsDouble("u", "sysupgrade"))
upService := upgrade.NewUpgradeService(
grapher, aurCache, config.Runtime.AURClient,
dbExecutor, config.Runtime.VCSStore, config.Runtime, config, settings.NoConfirm, config.Runtime.Logger.Child("upgrade"))
graph, errSysUp = upService.GraphUpgrades(ctx, graph, cmdArgs.ExistsDouble("u", "sysupgrade"))
if errSysUp != nil {
return errSysUp
}

View file

@ -3,23 +3,21 @@ package main
import (
"context"
"fmt"
"os"
"sort"
"strings"
"sync"
"github.com/Jguer/yay/v11/pkg/db"
"github.com/Jguer/yay/v11/pkg/dep"
"github.com/Jguer/yay/v11/pkg/intrange"
"github.com/Jguer/yay/v11/pkg/multierror"
"github.com/Jguer/yay/v11/pkg/query"
"github.com/Jguer/yay/v11/pkg/settings"
"github.com/Jguer/yay/v11/pkg/stringset"
"github.com/Jguer/yay/v11/pkg/text"
"github.com/Jguer/yay/v11/pkg/topo"
"github.com/Jguer/yay/v11/pkg/upgrade"
aur "github.com/Jguer/aur"
"github.com/Jguer/aur/metadata"
alpm "github.com/Jguer/go-alpm/v2"
"github.com/leonelquinteros/gotext"
)
@ -27,9 +25,10 @@ import (
func filterUpdateList(list []db.Upgrade, filter upgrade.Filter) []db.Upgrade {
tmp := list[:0]
for _, pkg := range list {
if filter(pkg) {
tmp = append(tmp, pkg)
for i := range list {
up := &list[i]
if filter(up) {
tmp = append(tmp, *up)
}
}
@ -37,7 +36,7 @@ func filterUpdateList(list []db.Upgrade, filter upgrade.Filter) []db.Upgrade {
}
// upList returns lists of packages to upgrade from each source.
func upList(ctx context.Context, aurCache settings.AURCache,
func upList(ctx context.Context,
warnings *query.AURWarnings, dbExecutor db.Executor, enableDowngrade bool,
filter upgrade.Filter,
) (aurUp, repoUp upgrade.UpSlice, err error) {
@ -45,10 +44,10 @@ func upList(ctx context.Context, aurCache settings.AURCache,
remoteNames := dbExecutor.InstalledRemotePackageNames()
var (
wg sync.WaitGroup
develUp upgrade.UpSlice
repoSlice []db.Upgrade
errs multierror.MultiError
wg sync.WaitGroup
develUp upgrade.UpSlice
syncUpgrades map[string]db.SyncUpgrade
errs multierror.MultiError
)
aurdata := make(map[string]*aur.Pkg)
@ -64,7 +63,7 @@ func upList(ctx context.Context, aurCache settings.AURCache,
wg.Add(1)
go func() {
repoSlice, err = dbExecutor.RepoUpgrades(enableDowngrade)
syncUpgrades, err = dbExecutor.SyncUpgrades(enableDowngrade)
errs.Add(err)
wg.Done()
}()
@ -74,11 +73,7 @@ func upList(ctx context.Context, aurCache settings.AURCache,
text.OperationInfoln(gotext.Get("Searching AUR for updates..."))
var _aurdata []aur.Pkg
if aurCache != nil {
_aurdata, err = aurCache.Get(ctx, &metadata.AURQuery{Needles: remoteNames, By: aur.Name})
} else {
_aurdata, err = query.AURInfo(ctx, config.Runtime.AURClient, remoteNames, warnings, config.RequestSplitN)
}
_aurdata, err = query.AURInfo(ctx, config.Runtime.AURClient, remoteNames, warnings, config.RequestSplitN)
errs.Add(err)
@ -129,7 +124,25 @@ func upList(ctx context.Context, aurCache settings.AURCache,
aurUp = develUp
aurUp.Repos = []string{"aur", "devel"}
repoUp = upgrade.UpSlice{Up: repoSlice, Repos: dbExecutor.Repos()}
repoUp = upgrade.UpSlice{
Up: make([]db.Upgrade, 0, len(syncUpgrades)),
Repos: dbExecutor.Repos(),
}
for _, up := range syncUpgrades {
dbUp := db.Upgrade{
Name: up.Package.Name(),
RemoteVersion: up.Package.Version(),
Repository: up.Package.DB().Name(),
Base: up.Package.Base(),
LocalVersion: up.LocalVersion,
Reason: up.Reason,
}
if filter != nil && !filter(&dbUp) {
continue
}
repoUp.Up = append(repoUp.Up, dbUp)
}
aurUp.Up = filterUpdateList(aurUp.Up, filter)
repoUp.Up = filterUpdateList(repoUp.Up, filter)
@ -195,11 +208,11 @@ func upgradePkgsMenu(aurUp, repoUp upgrade.UpSlice) (stringset.StringSet, []stri
allUp := upgrade.UpSlice{Up: append(repoUp.Up, aurUp.Up...), Repos: append(repoUp.Repos, aurUp.Repos...)}
fmt.Printf("%s"+text.Bold(" %d ")+"%s\n", text.Bold(text.Cyan("::")), allUpLen, text.Bold(gotext.Get("Packages to upgrade.")))
allUp.Print()
allUp.Print(config.Runtime.Logger)
text.Infoln(gotext.Get("Packages to exclude") + " (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name):")
numbers, err := text.GetInput(config.AnswerUpgrade, settings.NoConfirm)
numbers, err := text.GetInput(os.Stdin, config.AnswerUpgrade, settings.NoConfirm)
if err != nil {
return nil, nil, err
}
@ -251,8 +264,8 @@ func sysupgradeTargets(ctx context.Context, dbExecutor db.Executor,
) (stringset.StringSet, []string, error) {
warnings := query.NewWarnings()
aurUp, repoUp, err := upList(ctx, nil, warnings, dbExecutor, enableDowngrade,
func(upgrade.Upgrade) bool { return true })
aurUp, repoUp, err := upList(ctx, warnings, dbExecutor, enableDowngrade,
func(*upgrade.Upgrade) bool { return true })
if err != nil {
return nil, nil, err
}
@ -261,85 +274,3 @@ func sysupgradeTargets(ctx context.Context, dbExecutor db.Executor,
return upgradePkgsMenu(aurUp, repoUp)
}
// Targets for sys upgrade.
func sysupgradeTargetsV2(ctx context.Context,
aurCache settings.AURCache,
dbExecutor db.Executor,
graph *topo.Graph[string, *dep.InstallInfo],
enableDowngrade bool,
) (*topo.Graph[string, *dep.InstallInfo], stringset.StringSet, error) {
warnings := query.NewWarnings()
aurUp, repoUp, err := upList(ctx, aurCache, warnings, dbExecutor, enableDowngrade,
func(upgrade.Upgrade) bool { return true })
if err != nil {
return graph, nil, err
}
warnings.Print()
ignore := make(stringset.StringSet)
allUpLen := len(repoUp.Up) + len(aurUp.Up)
if allUpLen == 0 {
return graph, ignore, nil
}
sort.Sort(repoUp)
sort.Sort(aurUp)
allUp := upgrade.UpSlice{Up: append(repoUp.Up, aurUp.Up...), Repos: append(repoUp.Repos, aurUp.Repos...)}
fmt.Printf("%s"+text.Bold(" %d ")+"%s\n", text.Bold(text.Cyan("::")), allUpLen, text.Bold(gotext.Get("Packages to upgrade.")))
allUp.Print()
text.Infoln(gotext.Get("Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)"))
numbers, err := text.GetInput(config.AnswerUpgrade, settings.NoConfirm)
if err != nil {
return nil, nil, err
}
// upgrade menu asks you which packages to NOT upgrade so in this case
// include and exclude are kind of swapped
include, exclude, otherInclude, otherExclude := intrange.ParseNumberMenu(numbers)
isInclude := len(exclude) == 0 && len(otherExclude) == 0
for i := range repoUp.Up {
pkg := &repoUp.Up[i]
if isInclude && otherInclude.Get(pkg.Repository) {
ignore.Set(pkg.Name)
}
if isInclude && !include.Get(len(repoUp.Up)-i+len(aurUp.Up)) {
dep.AddUpgradeToGraph(pkg, graph)
continue
}
if !isInclude && (exclude.Get(len(repoUp.Up)-i+len(aurUp.Up)) || otherExclude.Get(pkg.Repository)) {
dep.AddUpgradeToGraph(pkg, graph)
continue
}
ignore.Set(pkg.Name)
}
for i := range aurUp.Up {
pkg := &aurUp.Up[i]
if isInclude && otherInclude.Get(pkg.Repository) {
continue
}
if isInclude && !include.Get(len(aurUp.Up)-i) {
dep.AddUpgradeToGraph(pkg, graph)
}
if !isInclude && (exclude.Get(len(aurUp.Up)-i) || otherExclude.Get(pkg.Repository)) {
dep.AddUpgradeToGraph(pkg, graph)
}
}
return graph, ignore, err
}