Przeglądaj źródła

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
Jo 2 lat temu
rodzic
commit
0bf4c2e502

+ 1 - 1
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

+ 4 - 4
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
 	}

+ 4 - 4
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

+ 8 - 0
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=

+ 0 - 2
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

+ 8 - 1
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

+ 15 - 17
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 {

+ 38 - 14
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")
 }

+ 1 - 1
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)
 

+ 62 - 45
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,
-		},
-	})
-}

+ 1 - 1
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

+ 1 - 1
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
 	}

+ 1 - 3
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()
 	}
 

+ 1 - 3
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

+ 1 - 0
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

+ 2 - 0
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
 }

+ 7 - 3
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)
+}

+ 13 - 19
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 {

+ 84 - 0
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...)
+}

+ 46 - 107
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
-	g.nodes[node] = true
-
-	// add alias
-	if _, ok := g.alias[alias]; ok {
-		return ErrConflictingAlias
-	}
-
-	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
+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
+		}
 	}
 
-	return node
+	return nil
 }
 
 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,28 +187,42 @@ 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
 	}
 }
 
-func (g *Graph[T, V]) remove(node T) {
+// 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] {
-		g.dependencies.removeFromDepmap(dependent, 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] {
-		g.dependents.removeFromDepmap(dependency, node)
+		last := g.dependents.removeFromDepmap(dependency, node)
+		text.Debugln("pruning dependency", dependency, last)
+		if last {
+			g.Prune(dependency)
+		}
 	}
 
 	delete(g.dependencies, node)
@@ -293,22 +231,23 @@ 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)
+func (g *Graph[T, V]) remove(node T) {
+	// Remove edges from things that depend on `node`.
+	for dependent := range g.dependents[node] {
+		g.dependencies.removeFromDepmap(dependent, node)
 	}
 
-	allNodes := make([]T, 0, nodeCount)
+	delete(g.dependents, node)
 
-	for _, layer := range layers {
-		allNodes = append(allNodes, layer...)
+	// Remove all edges from node to the things it depends on.
+	for dependency := range g.dependencies[node] {
+		g.dependents.removeFromDepmap(dependency, node)
 	}
 
-	return allNodes
+	delete(g.dependencies, node)
+
+	// Finally, remove the node itself.
+	delete(g.nodes, node)
 }
 
 func (g *Graph[T, V]) Dependencies(child T) NodeSet[T] {

+ 303 - 0
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
+}

+ 287 - 0
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)
+			}
+		})
+	}
+}

+ 5 - 5
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)
 	}
 }

+ 2 - 2
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 {

+ 6 - 1
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
 		}

+ 35 - 104
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
-}