From 0bf4c2e502354f79a676eb7eb856d0c54d2e6741 Mon Sep 17 00:00:00 2001 From: Jo Date: Fri, 17 Feb 2023 20:01:26 +0100 Subject: [PATCH] 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 --- ci.Dockerfile | 2 +- cmd.go | 8 +- go.mod | 8 +- go.sum | 8 + pkg/cmd/graph/main.go | 2 - pkg/db/executor.go | 9 +- pkg/db/ialpm/alpm.go | 32 ++-- pkg/db/mock/executor.go | 52 +++++-- pkg/dep/depPool.go | 2 +- pkg/dep/dep_graph.go | 107 +++++++------ pkg/menus/edit_menu.go | 2 +- pkg/menus/menu.go | 2 +- pkg/query/aur_info.go | 4 +- pkg/query/source.go | 4 +- pkg/settings/config.go | 1 + pkg/settings/runtime.go | 2 + pkg/text/input.go | 10 +- pkg/text/print.go | 32 ++-- pkg/text/service.go | 84 ++++++++++ pkg/topo/dep.go | 163 ++++++------------- pkg/upgrade/service.go | 303 ++++++++++++++++++++++++++++++++++++ pkg/upgrade/service_test.go | 287 ++++++++++++++++++++++++++++++++++ pkg/upgrade/upgrade.go | 10 +- print.go | 4 +- sync.go | 7 +- upgrade.go | 139 +++++------------ 26 files changed, 941 insertions(+), 343 deletions(-) create mode 100644 pkg/text/service.go create mode 100644 pkg/upgrade/service.go create mode 100644 pkg/upgrade/service_test.go diff --git a/ci.Dockerfile b/ci.Dockerfile index ae6300ca..c6964c71 100644 --- a/ci.Dockerfile +++ b/ci.Dockerfile @@ -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 diff --git a/cmd.go b/cmd.go index 3618ce47..879824f8 100644 --- a/cmd.go +++ b/cmd.go @@ -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 } diff --git a/go.mod b/go.mod index 30b16270..c537617b 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 36a54872..f29daf9a 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/pkg/cmd/graph/main.go b/pkg/cmd/graph/main.go index 75580db1..fd179455 100644 --- a/pkg/cmd/graph/main.go +++ b/pkg/cmd/graph/main.go @@ -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 diff --git a/pkg/db/executor.go b/pkg/db/executor.go index ee15c64b..4d48c073 100644 --- a/pkg/db/executor.go +++ b/pkg/db/executor.go @@ -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 diff --git a/pkg/db/ialpm/alpm.go b/pkg/db/ialpm/alpm.go index 5eb2de5b..68861fee 100644 --- a/pkg/db/ialpm/alpm.go +++ b/pkg/db/ialpm/alpm.go @@ -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 { diff --git a/pkg/db/mock/executor.go b/pkg/db/mock/executor.go index cf3c7493..79d32e13 100644 --- a/pkg/db/mock/executor.go +++ b/pkg/db/mock/executor.go @@ -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") } diff --git a/pkg/dep/depPool.go b/pkg/dep/depPool.go index ec895c19..e627ebd6 100644 --- a/pkg/dep/depPool.go +++ b/pkg/dep/depPool.go @@ -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) diff --git a/pkg/dep/dep_graph.go b/pkg/dep/dep_graph.go index 92280d33..1d0886c9 100644 --- a/pkg/dep/dep_graph.go +++ b/pkg/dep/dep_graph.go @@ -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, - }, - }) -} diff --git a/pkg/menus/edit_menu.go b/pkg/menus/edit_menu.go index e6a6b226..b9792b63 100644 --- a/pkg/menus/edit_menu.go +++ b/pkg/menus/edit_menu.go @@ -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 diff --git a/pkg/menus/menu.go b/pkg/menus/menu.go index 5b6a513b..b8f01735 100644 --- a/pkg/menus/menu.go +++ b/pkg/menus/menu.go @@ -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 } diff --git a/pkg/query/aur_info.go b/pkg/query/aur_info.go index 6849c8a9..837e8f3a 100644 --- a/pkg/query/aur_info.go +++ b/pkg/query/aur_info.go @@ -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() } diff --git a/pkg/query/source.go b/pkg/query/source.go index a927e5ab..c4d67952 100644 --- a/pkg/query/source.go +++ b/pkg/query/source.go @@ -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 diff --git a/pkg/settings/config.go b/pkg/settings/config.go index 89a2a858..8f4d5dac 100644 --- a/pkg/settings/config.go +++ b/pkg/settings/config.go @@ -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 diff --git a/pkg/settings/runtime.go b/pkg/settings/runtime.go index 1bd22b35..d0da33bc 100644 --- a/pkg/settings/runtime.go +++ b/pkg/settings/runtime.go @@ -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 } diff --git a/pkg/text/input.go b/pkg/text/input.go index 1d398153..b93ad860 100644 --- a/pkg/text/input.go +++ b/pkg/text/input.go @@ -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) +} diff --git a/pkg/text/print.go b/pkg/text/print.go index 3f1b5daa..8a263c84 100644 --- a/pkg/text/print.go +++ b/pkg/text/print.go @@ -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 { diff --git a/pkg/text/service.go b/pkg/text/service.go new file mode 100644 index 00000000..ada552bd --- /dev/null +++ b/pkg/text/service.go @@ -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...) +} diff --git a/pkg/topo/dep.go b/pkg/topo/dep.go index fba097cd..73fd4720 100644 --- a/pkg/topo/dep.go +++ b/pkg/topo/dep.go @@ -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) } diff --git a/pkg/upgrade/service.go b/pkg/upgrade/service.go new file mode 100644 index 00000000..4e5ed574 --- /dev/null +++ b/pkg/upgrade/service.go @@ -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 +} diff --git a/pkg/upgrade/service_test.go b/pkg/upgrade/service_test.go new file mode 100644 index 00000000..057aac7b --- /dev/null +++ b/pkg/upgrade/service_test.go @@ -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) + } + }) + } +} diff --git a/pkg/upgrade/upgrade.go b/pkg/upgrade/upgrade.go index 6128b62b..1ffba628 100644 --- a/pkg/upgrade/upgrade.go +++ b/pkg/upgrade/upgrade.go @@ -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) } } diff --git a/print.go b/print.go index e1b88e7e..492c74d9 100644 --- a/print.go +++ b/print.go @@ -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 { diff --git a/sync.go b/sync.go index 64a178f0..de1b822f 100644 --- a/sync.go +++ b/sync.go @@ -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 } diff --git a/upgrade.go b/upgrade.go index da7683ec..66dc9756 100644 --- a/upgrade.go +++ b/upgrade.go @@ -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 -}