소스 검색

fix: rework menus to work on both flows (#1830)

* rework menus to work on both flows

* add installed package split

* remove unused field

* Add post install hooks
Jo 2 년 전
부모
커밋
6ad63cae10
17개의 변경된 파일388개의 추가작업 그리고 195개의 파일을 삭제
  1. 6 3
      clean.go
  2. 25 28
      install.go
  3. 3 0
      pkg/db/executor.go
  4. 17 4
      pkg/db/ialpm/alpm.go
  5. 46 0
      pkg/db/ialpm/high_level.go
  6. 3 1
      pkg/db/mock/executor.go
  7. 65 14
      pkg/menus/clean_menu.go
  8. 65 34
      pkg/menus/diff_menu.go
  9. 50 13
      pkg/menus/edit_menu.go
  10. 25 28
      pkg/menus/menu.go
  11. 0 30
      pkg/query/filter.go
  12. 4 0
      pkg/settings/exe/exec.go
  13. 68 19
      preparer.go
  14. 3 10
      print.go
  15. 5 5
      sync.go
  16. 2 1
      upgrade.go
  17. 1 5
      vcs.go

+ 6 - 3
clean.go

@@ -99,7 +99,7 @@ func cleanAUR(ctx context.Context, keepInstalled, keepCurrent, removeAll bool, d
 	installedBases := make(stringset.StringSet)
 	inAURBases := make(stringset.StringSet)
 
-	remotePackages, _ := query.GetRemotePackages(dbExecutor)
+	remotePackages := dbExecutor.InstalledRemotePackages()
 
 	files, err := os.ReadDir(config.BuildDir)
 	if err != nil {
@@ -194,10 +194,11 @@ func isGitRepository(dir string) bool {
 	return !os.IsNotExist(err)
 }
 
-func cleanAfter(ctx context.Context, cmdBuilder exe.ICmdBuilder, pkgbuildDirs []string) {
+func cleanAfter(ctx context.Context, cmdBuilder exe.ICmdBuilder, pkgbuildDirs map[string]string) {
 	fmt.Println(gotext.Get("removing untracked AUR files from cache..."))
 
-	for i, dir := range pkgbuildDirs {
+	i := 0
+	for _, dir := range pkgbuildDirs {
 		text.OperationInfoln(gotext.Get("Cleaning (%d/%d): %s", i+1, len(pkgbuildDirs), text.Cyan(dir)))
 
 		_, stderr, err := cmdBuilder.Capture(
@@ -212,5 +213,7 @@ func cleanAfter(ctx context.Context, cmdBuilder exe.ICmdBuilder, pkgbuildDirs []
 				ctx, dir, "clean", "-fx", "--exclude='*.pkg.*'")); err != nil {
 			fmt.Fprintln(os.Stderr, err)
 		}
+
+		i++
 	}
 }

+ 25 - 28
install.go

@@ -12,6 +12,7 @@ import (
 
 	alpm "github.com/Jguer/go-alpm/v2"
 	gosrc "github.com/Morganamilo/go-srcinfo"
+	mapset "github.com/deckarep/golang-set/v2"
 	"github.com/leonelquinteros/gotext"
 
 	"github.com/Jguer/yay/v11/pkg/completion"
@@ -109,12 +110,10 @@ func install(ctx context.Context, cmdArgs *parser.Arguments, dbExecutor db.Execu
 		return errRefresh
 	}
 
-	localNames, remoteNames, err := query.GetPackageNamesBySource(dbExecutor)
-	if err != nil {
-		return err
-	}
+	remoteNames := dbExecutor.InstalledRemotePackageNames()
+	localNames := dbExecutor.InstalledSyncPackageNames()
 
-	remoteNamesCache := stringset.FromSlice(remoteNames)
+	remoteNamesCache := mapset.NewThreadUnsafeSet(remoteNames...)
 	localNamesCache := stringset.FromSlice(localNames)
 
 	requestTargets := cmdArgs.Copy().Targets
@@ -197,17 +196,17 @@ func install(ctx context.Context, cmdArgs *parser.Arguments, dbExecutor db.Execu
 	do.Print()
 	fmt.Println()
 
-	if config.CleanAfter {
-		defer func() {
-			pkgbuildDirs := make([]string, 0, len(do.Aur))
+	pkgbuildDirs := make(map[string]string, len(do.Aur))
 
-			for _, base := range do.Aur {
-				dir := filepath.Join(config.BuildDir, base.Pkgbase())
-				if isGitRepository(dir) {
-					pkgbuildDirs = append(pkgbuildDirs, dir)
-				}
-			}
+	for _, base := range do.Aur {
+		dir := filepath.Join(config.BuildDir, base.Pkgbase())
+		if isGitRepository(dir) {
+			pkgbuildDirs[base.Pkgbase()] = dir
+		}
+	}
 
+	if config.CleanAfter {
+		defer func() {
 			cleanAfter(ctx, config.Runtime.CmdBuilder, pkgbuildDirs)
 		}()
 	}
@@ -230,8 +229,8 @@ func install(ctx context.Context, cmdArgs *parser.Arguments, dbExecutor db.Execu
 		}
 	}
 
-	if errCleanMenu := menus.Clean(config.CleanMenu,
-		config.BuildDir, do.Aur,
+	if errCleanMenu := menus.Clean(os.Stdout, config.CleanMenu,
+		pkgbuildDirs,
 		remoteNamesCache, settings.NoConfirm, config.AnswerClean); errCleanMenu != nil {
 		if errors.As(errCleanMenu, &settings.ErrUserAbort{}) {
 			return errCleanMenu
@@ -261,8 +260,8 @@ func install(ctx context.Context, cmdArgs *parser.Arguments, dbExecutor db.Execu
 		return errA
 	}
 
-	if errDiffMenu := menus.Diff(ctx, config.Runtime.CmdBuilder, config.BuildDir,
-		config.DiffMenu, do.Aur, remoteNamesCache,
+	if errDiffMenu := menus.Diff(ctx, config.Runtime.CmdBuilder, os.Stdout, pkgbuildDirs,
+		config.DiffMenu, remoteNamesCache,
 		cloned, settings.NoConfirm, config.AnswerDiff); errDiffMenu != nil {
 		if errors.As(errDiffMenu, &settings.ErrUserAbort{}) {
 			return errDiffMenu
@@ -280,7 +279,7 @@ func install(ctx context.Context, cmdArgs *parser.Arguments, dbExecutor db.Execu
 		return err
 	}
 
-	if errEditMenu := menus.Edit(config.EditMenu, config.BuildDir, do.Aur,
+	if errEditMenu := menus.Edit(os.Stdout, config.EditMenu, pkgbuildDirs,
 		config.Editor, config.EditorFlags, remoteNamesCache, srcinfos,
 		settings.NoConfirm, config.AnswerEdit); errEditMenu != nil {
 		if errors.As(errEditMenu, &settings.ErrUserAbort{}) {
@@ -315,7 +314,7 @@ func install(ctx context.Context, cmdArgs *parser.Arguments, dbExecutor db.Execu
 		exp := make([]string, 0)
 
 		for _, pkg := range do.Repo {
-			if !dp.Explicit.Get(pkg.Name()) && !localNamesCache.Get(pkg.Name()) && !remoteNamesCache.Get(pkg.Name()) {
+			if !dp.Explicit.Get(pkg.Name()) && !localNamesCache.Get(pkg.Name()) && !remoteNamesCache.Contains(pkg.Name()) {
 				deps = append(deps, pkg.Name())
 
 				continue
@@ -630,10 +629,8 @@ func buildInstallPkgbuilds(
 	settings.NoConfirm = true
 
 	// remotenames: names of all non repo packages on the system
-	localNames, remoteNames, err := query.GetPackageNamesBySource(dbExecutor)
-	if err != nil {
-		return err
-	}
+	remoteNames := dbExecutor.InstalledRemotePackageNames()
+	localNames := dbExecutor.InstalledSyncPackageNames()
 
 	// cache as a stringset. maybe make it return a string set in the first
 	// place
@@ -688,7 +685,7 @@ func buildInstallPkgbuilds(
 		}
 
 		// pkgver bump
-		if err = config.Runtime.CmdBuilder.Show(
+		if err := config.Runtime.CmdBuilder.Show(
 			config.Runtime.CmdBuilder.BuildMakepkgCmd(ctx, dir, args...)); err != nil {
 			return errors.New(gotext.Get("error making: %s", base.String()))
 		}
@@ -727,7 +724,7 @@ func buildInstallPkgbuilds(
 			}
 
 			if installed {
-				err = config.Runtime.CmdBuilder.Show(
+				err := config.Runtime.CmdBuilder.Show(
 					config.Runtime.CmdBuilder.BuildMakepkgCmd(ctx,
 						dir, "-c", "--nobuild", "--noextract", "--ignorearch"))
 				if err != nil {
@@ -741,7 +738,7 @@ func buildInstallPkgbuilds(
 		}
 
 		if built {
-			err = config.Runtime.CmdBuilder.Show(
+			err := config.Runtime.CmdBuilder.Show(
 				config.Runtime.CmdBuilder.BuildMakepkgCmd(ctx,
 					dir, "-c", "--nobuild", "--noextract", "--ignorearch"))
 			if err != nil {
@@ -816,7 +813,7 @@ func buildInstallPkgbuilds(
 
 	settings.NoConfirm = oldConfirm
 
-	return err
+	return nil
 }
 
 func installPkgArchive(ctx context.Context, cmdArgs *parser.Arguments, pkgArchives []string) error {

+ 3 - 0
pkg/db/executor.go

@@ -30,6 +30,9 @@ type Executor interface {
 	AlpmArchitectures() ([]string, error)
 	BiggestPackages() []IPackage
 	Cleanup()
+	InstalledRemotePackageNames() []string
+	InstalledRemotePackages() []IPackage
+	InstalledSyncPackageNames() []string
 	IsCorrectVersionInstalled(string, string) bool
 	LastBuildTime() time.Time
 	LocalPackage(string) IPackage

+ 17 - 4
pkg/db/ialpm/alpm.go

@@ -23,16 +23,29 @@ type AlpmExecutor struct {
 	syncDB       alpm.IDBList
 	syncDBsCache []alpm.IDB
 	conf         *pacmanconf.Config
+
+	installedRemotePkgs     []alpm.IPackage
+	installedRemotePkgNames []string
+	installedSyncPkgNames   []string
 }
 
 func NewExecutor(pacmanConf *pacmanconf.Config) (*AlpmExecutor, error) {
-	ae := &AlpmExecutor{conf: pacmanConf}
-
-	err := ae.RefreshHandle()
-	if err != nil {
+	ae := &AlpmExecutor{
+		handle:                  nil,
+		localDB:                 nil,
+		syncDB:                  nil,
+		syncDBsCache:            []alpm.IDB{},
+		conf:                    pacmanConf,
+		installedRemotePkgs:     nil,
+		installedRemotePkgNames: nil,
+		installedSyncPkgNames:   nil,
+	}
+
+	if err := ae.RefreshHandle(); err != nil {
 		return nil, err
 	}
 
+	var err error
 	ae.localDB, err = ae.handle.LocalDB()
 	if err != nil {
 		return nil, err

+ 46 - 0
pkg/db/ialpm/high_level.go

@@ -0,0 +1,46 @@
+package ialpm
+
+import (
+	"github.com/Jguer/yay/v11/pkg/db"
+	"github.com/Jguer/yay/v11/pkg/text"
+)
+
+// GetPackageNamesBySource returns package names with and without correspondence in SyncDBS respectively.
+func (ae *AlpmExecutor) getPackageNamesBySource() {
+	for _, localpkg := range ae.LocalPackages() {
+		pkgName := localpkg.Name()
+		if ae.SyncPackage(pkgName) != nil {
+			ae.installedSyncPkgNames = append(ae.installedSyncPkgNames, pkgName)
+		} else {
+			ae.installedRemotePkgs = append(ae.installedRemotePkgs, localpkg)
+			ae.installedRemotePkgNames = append(ae.installedRemotePkgNames, pkgName)
+		}
+	}
+
+	text.Debugln("populating db executor package caches.",
+		"sync_len", len(ae.installedSyncPkgNames), "remote_len", len(ae.installedRemotePkgNames))
+}
+
+func (ae *AlpmExecutor) InstalledRemotePackages() []db.IPackage {
+	if ae.installedRemotePkgs == nil {
+		ae.getPackageNamesBySource()
+	}
+
+	return ae.installedRemotePkgs
+}
+
+func (ae *AlpmExecutor) InstalledRemotePackageNames() []string {
+	if ae.installedRemotePkgNames == nil {
+		ae.getPackageNamesBySource()
+	}
+
+	return ae.installedRemotePkgNames
+}
+
+func (ae *AlpmExecutor) InstalledSyncPackageNames() []string {
+	if ae.installedSyncPkgNames == nil {
+		ae.getPackageNamesBySource()
+	}
+
+	return ae.installedSyncPkgNames
+}

+ 3 - 1
pkg/db/mock/executor.go

@@ -14,7 +14,9 @@ type (
 	Upgrade  = db.Upgrade
 )
 
-type DBExecutor struct{}
+type DBExecutor struct {
+	db.Executor
+}
 
 func (t DBExecutor) AlpmArchitectures() ([]string, error) {
 	panic("implement me")

+ 65 - 14
pkg/menus/clean_menu.go

@@ -2,22 +2,20 @@
 package menus
 
 import (
+	"context"
 	"fmt"
+	"io"
 	"os"
-	"path/filepath"
 
+	mapset "github.com/deckarep/golang-set/v2"
 	"github.com/leonelquinteros/gotext"
 
-	"github.com/Jguer/yay/v11/pkg/dep"
-	"github.com/Jguer/yay/v11/pkg/stringset"
+	"github.com/Jguer/yay/v11/pkg/settings"
 	"github.com/Jguer/yay/v11/pkg/text"
 )
 
-func anyExistInCache(buildDir string, bases []dep.Base) bool {
-	for _, base := range bases {
-		pkg := base.Pkgbase()
-		dir := filepath.Join(buildDir, pkg)
-
+func anyExistInCache(pkgbuildDirs map[string]string) bool {
+	for _, dir := range pkgbuildDirs {
 		if _, err := os.Stat(dir); !os.IsNotExist(err) {
 			return true
 		}
@@ -26,14 +24,13 @@ func anyExistInCache(buildDir string, bases []dep.Base) bool {
 	return false
 }
 
-func Clean(cleanMenuOption bool, buildDir string, bases []dep.Base,
-	installed stringset.StringSet, noConfirm bool, answerClean string) error {
-	if !(cleanMenuOption && anyExistInCache(buildDir, bases)) {
+func CleanFn(ctx context.Context, config *settings.Configuration, w io.Writer, pkgbuildDirsByBase map[string]string) error {
+	if !anyExistInCache(pkgbuildDirsByBase) {
 		return nil
 	}
 
 	skipFunc := func(pkg string) bool {
-		dir := filepath.Join(buildDir, pkg)
+		dir := pkgbuildDirsByBase[pkg]
 		if _, err := os.Stat(dir); os.IsNotExist(err) {
 			return true
 		}
@@ -41,14 +38,68 @@ func Clean(cleanMenuOption bool, buildDir string, bases []dep.Base,
 		return false
 	}
 
-	toClean, errClean := selectionMenu(buildDir, bases, installed, gotext.Get("Packages to cleanBuild?"),
+	bases := make([]string, 0, len(pkgbuildDirsByBase))
+	for pkg := range pkgbuildDirsByBase {
+		bases = append(bases, pkg)
+	}
+
+	// TOFIX: empty installed slice means installed filter is disabled
+	toClean, errClean := selectionMenu(w, pkgbuildDirsByBase, bases, mapset.NewSet[string](),
+		gotext.Get("Packages to cleanBuild?"),
+		settings.NoConfirm, config.AnswerClean, skipFunc)
+	if errClean != nil {
+		return errClean
+	}
+
+	for i, base := range toClean {
+		dir := pkgbuildDirsByBase[base]
+		text.OperationInfoln(gotext.Get("Deleting (%d/%d): %s", i+1, len(toClean), text.Cyan(dir)))
+
+		if err := config.Runtime.CmdBuilder.Show(config.Runtime.CmdBuilder.BuildGitCmd(ctx, dir, "reset", "--hard")); err != nil {
+			text.Warnln(gotext.Get("Unable to clean:"), dir)
+
+			return err
+		}
+
+		if err := config.Runtime.CmdBuilder.Show(config.Runtime.CmdBuilder.BuildGitCmd(ctx, dir, "clean", "-fdx")); err != nil {
+			text.Warnln(gotext.Get("Unable to clean:"), dir)
+
+			return err
+		}
+	}
+
+	return nil
+}
+
+func Clean(w io.Writer, cleanMenuOption bool, pkgbuildDirs map[string]string,
+	installed mapset.Set[string], noConfirm bool, answerClean string,
+) error {
+	if !(cleanMenuOption && anyExistInCache(pkgbuildDirs)) {
+		return nil
+	}
+
+	skipFunc := func(pkg string) bool {
+		dir := pkgbuildDirs[pkg]
+		if _, err := os.Stat(dir); os.IsNotExist(err) {
+			return true
+		}
+
+		return false
+	}
+
+	bases := make([]string, 0, len(pkgbuildDirs))
+	for pkg := range pkgbuildDirs {
+		bases = append(bases, pkg)
+	}
+
+	toClean, errClean := selectionMenu(w, pkgbuildDirs, bases, installed, gotext.Get("Packages to cleanBuild?"),
 		noConfirm, answerClean, skipFunc)
 	if errClean != nil {
 		return errClean
 	}
 
 	for i, base := range toClean {
-		dir := filepath.Join(buildDir, base.Pkgbase())
+		dir := pkgbuildDirs[base]
 		text.OperationInfoln(gotext.Get("Deleting (%d/%d): %s", i+1, len(toClean), text.Cyan(dir)))
 
 		if err := os.RemoveAll(dir); err != nil {

+ 65 - 34
pkg/menus/diff_menu.go

@@ -4,17 +4,16 @@ package menus
 import (
 	"context"
 	"fmt"
+	"io"
 	"os"
-	"path/filepath"
 	"strings"
 
+	mapset "github.com/deckarep/golang-set/v2"
 	"github.com/leonelquinteros/gotext"
 
-	"github.com/Jguer/yay/v11/pkg/dep"
 	"github.com/Jguer/yay/v11/pkg/multierror"
 	"github.com/Jguer/yay/v11/pkg/settings"
 	"github.com/Jguer/yay/v11/pkg/settings/exe"
-	"github.com/Jguer/yay/v11/pkg/stringset"
 	"github.com/Jguer/yay/v11/pkg/text"
 )
 
@@ -23,24 +22,23 @@ const (
 	gitDiffRefName = "AUR_SEEN"
 )
 
-func showPkgbuildDiffs(ctx context.Context, cmdBuilder exe.ICmdBuilder, buildDir string, bases []dep.Base, cloned map[string]bool) error {
+func showPkgbuildDiffs(ctx context.Context, cmdBuilder exe.ICmdBuilder,
+	pkgbuildDirs map[string]string, bases []string,
+) error {
 	var errMulti multierror.MultiError
 
-	for _, base := range bases {
-		pkg := base.Pkgbase()
-		dir := filepath.Join(buildDir, pkg)
+	for _, pkg := range bases {
+		dir := pkgbuildDirs[pkg]
 
-		start, err := getLastSeenHash(ctx, cmdBuilder, buildDir, pkg)
+		start, err := getLastSeenHash(ctx, cmdBuilder, dir)
 		if err != nil {
 			errMulti.Add(err)
 
 			continue
 		}
 
-		if cloned[pkg] {
-			start = gitEmptyTree
-		} else {
-			hasDiff, err := gitHasDiff(ctx, cmdBuilder, buildDir, pkg)
+		if start != gitEmptyTree {
+			hasDiff, err := gitHasDiff(ctx, cmdBuilder, dir)
 			if err != nil {
 				errMulti.Add(err)
 
@@ -48,7 +46,7 @@ func showPkgbuildDiffs(ctx context.Context, cmdBuilder exe.ICmdBuilder, buildDir
 			}
 
 			if !hasDiff {
-				text.Warnln(gotext.Get("%s: No changes -- skipping", text.Cyan(base.String())))
+				text.Warnln(gotext.Get("%s: No changes -- skipping", text.Cyan(pkg)))
 
 				continue
 			}
@@ -73,10 +71,10 @@ func showPkgbuildDiffs(ctx context.Context, cmdBuilder exe.ICmdBuilder, buildDir
 
 // Check whether or not a diff exists between the last reviewed diff and
 // HEAD@{upstream}.
-func gitHasDiff(ctx context.Context, cmdBuilder exe.ICmdBuilder, path, name string) (bool, error) {
-	if gitHasLastSeenRef(ctx, cmdBuilder, path, name) {
+func gitHasDiff(ctx context.Context, cmdBuilder exe.ICmdBuilder, dir string) (bool, error) {
+	if gitHasLastSeenRef(ctx, cmdBuilder, dir) {
 		stdout, stderr, err := cmdBuilder.Capture(
-			cmdBuilder.BuildGitCmd(ctx, filepath.Join(path, name), "rev-parse", gitDiffRefName, "HEAD@{upstream}"))
+			cmdBuilder.BuildGitCmd(ctx, dir, "rev-parse", gitDiffRefName, "HEAD@{upstream}"))
 		if err != nil {
 			return false, fmt.Errorf("%s%s", stderr, err)
 		}
@@ -94,21 +92,21 @@ func gitHasDiff(ctx context.Context, cmdBuilder exe.ICmdBuilder, path, name stri
 
 // Return wether or not we have reviewed a diff yet. It checks for the existence of
 // YAY_DIFF_REVIEW in the git ref-list.
-func gitHasLastSeenRef(ctx context.Context, cmdBuilder exe.ICmdBuilder, path, name string) bool {
+func gitHasLastSeenRef(ctx context.Context, cmdBuilder exe.ICmdBuilder, dir string) bool {
 	_, _, err := cmdBuilder.Capture(
 		cmdBuilder.BuildGitCmd(ctx,
-			filepath.Join(path, name), "rev-parse", "--quiet", "--verify", gitDiffRefName))
+			dir, "rev-parse", "--quiet", "--verify", gitDiffRefName))
 
 	return err == nil
 }
 
 // Returns the last reviewed hash. If YAY_DIFF_REVIEW exists it will return this hash.
 // If it does not it will return empty tree as no diff have been reviewed yet.
-func getLastSeenHash(ctx context.Context, cmdBuilder exe.ICmdBuilder, path, name string) (string, error) {
-	if gitHasLastSeenRef(ctx, cmdBuilder, path, name) {
+func getLastSeenHash(ctx context.Context, cmdBuilder exe.ICmdBuilder, dir string) (string, error) {
+	if gitHasLastSeenRef(ctx, cmdBuilder, dir) {
 		stdout, stderr, err := cmdBuilder.Capture(
 			cmdBuilder.BuildGitCmd(ctx,
-				filepath.Join(path, name), "rev-parse", gitDiffRefName))
+				dir, "rev-parse", gitDiffRefName))
 		if err != nil {
 			return "", fmt.Errorf("%s %s", stderr, err)
 		}
@@ -123,10 +121,10 @@ func getLastSeenHash(ctx context.Context, cmdBuilder exe.ICmdBuilder, path, name
 
 // Update the YAY_DIFF_REVIEW ref to HEAD. We use this ref to determine which diff were
 // reviewed by the user.
-func gitUpdateSeenRef(ctx context.Context, cmdBuilder exe.ICmdBuilder, path, name string) error {
+func gitUpdateSeenRef(ctx context.Context, cmdBuilder exe.ICmdBuilder, dir string) error {
 	_, stderr, err := cmdBuilder.Capture(
 		cmdBuilder.BuildGitCmd(ctx,
-			filepath.Join(path, name), "update-ref", gitDiffRefName, "HEAD"))
+			dir, "update-ref", gitDiffRefName, "HEAD"))
 	if err != nil {
 		return fmt.Errorf("%s %s", stderr, err)
 	}
@@ -134,13 +132,12 @@ func gitUpdateSeenRef(ctx context.Context, cmdBuilder exe.ICmdBuilder, path, nam
 	return nil
 }
 
-func updatePkgbuildSeenRef(ctx context.Context, cmdBuilder exe.ICmdBuilder, buildDir string, bases []dep.Base) error {
+func updatePkgbuildSeenRef(ctx context.Context, cmdBuilder exe.ICmdBuilder, pkgbuildDirs map[string]string, bases []string) error {
 	var errMulti multierror.MultiError
 
-	for _, base := range bases {
-		pkg := base.Pkgbase()
-
-		if err := gitUpdateSeenRef(ctx, cmdBuilder, buildDir, pkg); err != nil {
+	for _, pkg := range bases {
+		dir := pkgbuildDirs[pkg]
+		if err := gitUpdateSeenRef(ctx, cmdBuilder, dir); err != nil {
 			errMulti.Add(err)
 		}
 	}
@@ -148,21 +145,55 @@ func updatePkgbuildSeenRef(ctx context.Context, cmdBuilder exe.ICmdBuilder, buil
 	return errMulti.Return()
 }
 
-func Diff(ctx context.Context, cmdBuilder exe.ICmdBuilder,
-	buildDir string, diffMenuOption bool, bases []dep.Base,
-	installed stringset.StringSet, cloned map[string]bool, noConfirm bool, diffDefaultAnswer string,
+func Diff(ctx context.Context, cmdBuilder exe.ICmdBuilder, w io.Writer,
+	pkgbuildDirs map[string]string, diffMenuOption bool,
+	installed mapset.Set[string], cloned map[string]bool, noConfirm bool, diffDefaultAnswer string,
 ) error {
 	if !diffMenuOption {
 		return nil
 	}
 
-	toDiff, errMenu := selectionMenu(buildDir, bases, installed, gotext.Get("Diffs to show?"),
+	bases := make([]string, 0, len(pkgbuildDirs))
+	for base := range pkgbuildDirs {
+		bases = append(bases, base)
+	}
+
+	toDiff, errMenu := selectionMenu(w, pkgbuildDirs, bases, installed, gotext.Get("Diffs to show?"),
 		noConfirm, diffDefaultAnswer, nil)
 	if errMenu != nil || len(toDiff) == 0 {
 		return errMenu
 	}
 
-	if errD := showPkgbuildDiffs(ctx, cmdBuilder, buildDir, toDiff, cloned); errD != nil {
+	if errD := showPkgbuildDiffs(ctx, cmdBuilder, pkgbuildDirs, toDiff); errD != nil {
+		return errD
+	}
+
+	fmt.Println()
+
+	if !text.ContinueTask(os.Stdin, gotext.Get("Proceed with install?"), true, false) {
+		return settings.ErrUserAbort{}
+	}
+
+	if errUpd := updatePkgbuildSeenRef(ctx, cmdBuilder, pkgbuildDirs, toDiff); errUpd != nil {
+		return errUpd
+	}
+
+	return nil
+}
+
+func DiffFn(ctx context.Context, config *settings.Configuration, w io.Writer, pkgbuildDirsByBase map[string]string) error {
+	bases := make([]string, 0, len(pkgbuildDirsByBase))
+	for base := range pkgbuildDirsByBase {
+		bases = append(bases, base)
+	}
+
+	toDiff, errMenu := selectionMenu(w, pkgbuildDirsByBase, bases, mapset.NewThreadUnsafeSet[string](), gotext.Get("Diffs to show?"),
+		settings.NoConfirm, config.AnswerDiff, nil)
+	if errMenu != nil || len(toDiff) == 0 {
+		return errMenu
+	}
+
+	if errD := showPkgbuildDiffs(ctx, config.Runtime.CmdBuilder, pkgbuildDirsByBase, toDiff); errD != nil {
 		return errD
 	}
 
@@ -172,7 +203,7 @@ func Diff(ctx context.Context, cmdBuilder exe.ICmdBuilder,
 		return settings.ErrUserAbort{}
 	}
 
-	if errUpd := updatePkgbuildSeenRef(ctx, cmdBuilder, buildDir, toDiff); errUpd != nil {
+	if errUpd := updatePkgbuildSeenRef(ctx, config.Runtime.CmdBuilder, pkgbuildDirsByBase, toDiff); errUpd != nil {
 		return errUpd
 	}
 

+ 50 - 13
pkg/menus/edit_menu.go

@@ -2,19 +2,20 @@
 package menus
 
 import (
+	"context"
 	"errors"
 	"fmt"
+	"io"
 	"os"
 	"os/exec"
 	"path/filepath"
 	"strings"
 
 	gosrc "github.com/Morganamilo/go-srcinfo"
+	mapset "github.com/deckarep/golang-set/v2"
 	"github.com/leonelquinteros/gotext"
 
-	"github.com/Jguer/yay/v11/pkg/dep"
 	"github.com/Jguer/yay/v11/pkg/settings"
-	"github.com/Jguer/yay/v11/pkg/stringset"
 	"github.com/Jguer/yay/v11/pkg/text"
 )
 
@@ -82,19 +83,20 @@ func editor(editorConfig, editorFlags string, noConfirm bool) (editor string, ar
 	}
 }
 
-func editPkgbuilds(buildDir string, bases []dep.Base, editorConfig,
+func editPkgbuilds(pkgbuildDirs map[string]string, bases []string, editorConfig,
 	editorFlags string, srcinfos map[string]*gosrc.Srcinfo, noConfirm bool,
 ) error {
 	pkgbuilds := make([]string, 0, len(bases))
 
-	for _, base := range bases {
-		pkg := base.Pkgbase()
-		dir := filepath.Join(buildDir, pkg)
+	for _, pkg := range bases {
+		dir := pkgbuildDirs[pkg]
 		pkgbuilds = append(pkgbuilds, filepath.Join(dir, "PKGBUILD"))
 
-		for _, splitPkg := range srcinfos[pkg].SplitPackages() {
-			if splitPkg.Install != "" {
-				pkgbuilds = append(pkgbuilds, filepath.Join(dir, splitPkg.Install))
+		if srcinfos != nil {
+			for _, splitPkg := range srcinfos[pkg].SplitPackages() {
+				if splitPkg.Install != "" {
+					pkgbuilds = append(pkgbuilds, filepath.Join(dir, splitPkg.Install))
+				}
 			}
 		}
 	}
@@ -113,21 +115,56 @@ func editPkgbuilds(buildDir string, bases []dep.Base, editorConfig,
 	return nil
 }
 
-func Edit(editMenuOption bool, buildDir string, bases []dep.Base, editorConfig,
-	editorFlags string, installed stringset.StringSet, srcinfos map[string]*gosrc.Srcinfo,
+func Edit(w io.Writer, editMenuOption bool, pkgbuildDirs map[string]string, editorConfig,
+	editorFlags string, installed mapset.Set[string], srcinfos map[string]*gosrc.Srcinfo,
 	noConfirm bool, editDefaultAnswer string,
 ) error {
 	if !editMenuOption {
 		return nil
 	}
 
-	toEdit, errMenu := selectionMenu(buildDir, bases,
+	bases := make([]string, 0, len(pkgbuildDirs))
+	for pkg := range pkgbuildDirs {
+		bases = append(bases, pkg)
+	}
+
+	toEdit, errMenu := selectionMenu(w, pkgbuildDirs, bases,
 		installed, gotext.Get("PKGBUILDs to edit?"), noConfirm, editDefaultAnswer, nil)
 	if errMenu != nil || len(toEdit) == 0 {
 		return errMenu
 	}
 
-	if errEdit := editPkgbuilds(buildDir, toEdit, editorConfig, editorFlags, srcinfos, noConfirm); errEdit != nil {
+	if errEdit := editPkgbuilds(pkgbuildDirs, toEdit, editorConfig, editorFlags, srcinfos, noConfirm); errEdit != nil {
+		return errEdit
+	}
+
+	fmt.Println()
+
+	if !text.ContinueTask(os.Stdin, gotext.Get("Proceed with install?"), true, false) {
+		return settings.ErrUserAbort{}
+	}
+
+	return nil
+}
+
+func EditFn(ctx context.Context, config *settings.Configuration, w io.Writer,
+	pkgbuildDirsByBase map[string]string,
+) error {
+	bases := make([]string, 0, len(pkgbuildDirsByBase))
+	for pkg := range pkgbuildDirsByBase {
+		bases = append(bases, pkg)
+	}
+
+	toEdit, errMenu := selectionMenu(w, pkgbuildDirsByBase, bases,
+		mapset.NewThreadUnsafeSet[string](),
+		gotext.Get("PKGBUILDs to edit?"), settings.NoConfirm, config.AnswerEdit, nil)
+	if errMenu != nil || len(toEdit) == 0 {
+		return errMenu
+	}
+
+	// TOFIX: remove or use srcinfo data
+	if errEdit := editPkgbuilds(pkgbuildDirsByBase,
+		toEdit, config.Editor, config.EditorFlags, nil, settings.NoConfirm); errEdit != nil {
 		return errEdit
 	}
 

+ 25 - 28
pkg/menus/menu.go

@@ -2,29 +2,27 @@ package menus
 
 import (
 	"fmt"
+	"io"
 	"os"
-	"path/filepath"
 
 	"github.com/leonelquinteros/gotext"
 
-	"github.com/Jguer/yay/v11/pkg/dep"
 	"github.com/Jguer/yay/v11/pkg/intrange"
 	"github.com/Jguer/yay/v11/pkg/settings"
-	"github.com/Jguer/yay/v11/pkg/stringset"
 	"github.com/Jguer/yay/v11/pkg/text"
+
+	mapset "github.com/deckarep/golang-set/v2"
 )
 
-func pkgbuildNumberMenu(buildDir string, bases []dep.Base, installed stringset.StringSet) {
+func pkgbuildNumberMenu(w io.Writer, pkgbuildDirs map[string]string, bases []string, installed mapset.Set[string]) {
 	toPrint := ""
 
-	for n, base := range bases {
-		pkg := base.Pkgbase()
-		dir := filepath.Join(buildDir, pkg)
-
-		toPrint += fmt.Sprintf(text.Magenta("%3d")+" %-40s", len(bases)-n,
-			text.Bold(base.String()))
+	for n, pkgBase := range bases {
+		dir := pkgbuildDirs[pkgBase]
+		toPrint += fmt.Sprintf(text.Magenta("%3d")+" %-40s", len(pkgbuildDirs)-n,
+			text.Bold(pkgBase))
 
-		if base.AnyIsInSet(installed) {
+		if installed.Contains(pkgBase) {
 			toPrint += text.Bold(text.Green(gotext.Get(" (Installed)")))
 		}
 
@@ -35,14 +33,15 @@ func pkgbuildNumberMenu(buildDir string, bases []dep.Base, installed stringset.S
 		toPrint += "\n"
 	}
 
-	fmt.Print(toPrint)
+	fmt.Fprint(w, toPrint)
 }
 
-func selectionMenu(buildDir string, bases []dep.Base, installed stringset.StringSet,
-	message string, noConfirm bool, defaultAnswer string, skipFunc func(string) bool) ([]dep.Base, error) {
-	selected := make([]dep.Base, 0)
+func selectionMenu(w io.Writer, pkgbuildDirs map[string]string, bases []string, installed mapset.Set[string],
+	message string, noConfirm bool, defaultAnswer string, skipFunc func(string) bool,
+) ([]string, error) {
+	selected := make([]string, 0)
 
-	pkgbuildNumberMenu(buildDir, bases, installed)
+	pkgbuildNumberMenu(w, pkgbuildDirs, bases, installed)
 
 	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"))))
@@ -63,40 +62,38 @@ func selectionMenu(buildDir string, bases []dep.Base, installed stringset.String
 		return selected, nil
 	}
 
-	for i, base := range bases {
-		pkg := base.Pkgbase()
-
-		if skipFunc != nil && skipFunc(pkg) {
+	for i, pkgBase := range bases {
+		if skipFunc != nil && skipFunc(pkgBase) {
 			continue
 		}
 
-		anyInstalled := base.AnyIsInSet(installed)
+		anyInstalled := installed.Contains(pkgBase)
 
 		if !eIsInclude && eExclude.Get(len(bases)-i) {
 			continue
 		}
 
 		if anyInstalled && (eOtherInclude.Get("i") || eOtherInclude.Get("installed")) {
-			selected = append(selected, base)
+			selected = append(selected, pkgBase)
 			continue
 		}
 
 		if !anyInstalled && (eOtherInclude.Get("no") || eOtherInclude.Get("notinstalled")) {
-			selected = append(selected, base)
+			selected = append(selected, pkgBase)
 			continue
 		}
 
 		if eOtherInclude.Get("a") || eOtherInclude.Get("all") {
-			selected = append(selected, base)
+			selected = append(selected, pkgBase)
 			continue
 		}
 
-		if eIsInclude && (eInclude.Get(len(bases)-i) || eOtherInclude.Get(pkg)) {
-			selected = append(selected, base)
+		if eIsInclude && (eInclude.Get(len(bases)-i) || eOtherInclude.Get(pkgBase)) {
+			selected = append(selected, pkgBase)
 		}
 
-		if !eIsInclude && (!eExclude.Get(len(bases)-i) && !eOtherExclude.Get(pkg)) {
-			selected = append(selected, base)
+		if !eIsInclude && (!eExclude.Get(len(bases)-i) && !eOtherExclude.Get(pkgBase)) {
+			selected = append(selected, pkgBase)
 		}
 	}
 

+ 0 - 30
pkg/query/filter.go

@@ -3,40 +3,10 @@ package query
 import (
 	"github.com/leonelquinteros/gotext"
 
-	"github.com/Jguer/yay/v11/pkg/db"
 	"github.com/Jguer/yay/v11/pkg/settings/parser"
 	"github.com/Jguer/yay/v11/pkg/text"
 )
 
-// GetPackageNamesBySource returns package names with and without correspondence in SyncDBS respectively.
-func GetPackageNamesBySource(dbExecutor db.Executor) (local, remote []string, err error) {
-	for _, localpkg := range dbExecutor.LocalPackages() {
-		pkgName := localpkg.Name()
-		if dbExecutor.SyncPackage(pkgName) != nil {
-			local = append(local, pkgName)
-		} else {
-			remote = append(remote, pkgName)
-		}
-	}
-
-	return local, remote, err
-}
-
-// GetRemotePackages returns packages with no correspondence in SyncDBS.
-func GetRemotePackages(dbExecutor db.Executor) (
-	remote []db.IPackage,
-	remoteNames []string) {
-	for _, localpkg := range dbExecutor.LocalPackages() {
-		pkgName := localpkg.Name()
-		if dbExecutor.SyncPackage(pkgName) == nil {
-			remote = append(remote, localpkg)
-			remoteNames = append(remoteNames, pkgName)
-		}
-	}
-
-	return remote, remoteNames
-}
-
 func RemoveInvalidTargets(targets []string, mode parser.TargetMode) []string {
 	filteredTargets := make([]string, 0)
 

+ 4 - 0
pkg/settings/exe/exec.go

@@ -4,6 +4,8 @@ import (
 	"os"
 	"os/exec"
 	"strings"
+
+	"github.com/Jguer/yay/v11/pkg/text"
 )
 
 type Runner interface {
@@ -15,10 +17,12 @@ type OSRunner struct{}
 
 func (r *OSRunner) Show(cmd *exec.Cmd) error {
 	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
+	text.Debugln("running", cmd.String())
 	return cmd.Run()
 }
 
 func (r *OSRunner) Capture(cmd *exec.Cmd) (stdout, stderr string, err error) {
+	text.Debugln("capturing", cmd.String())
 	outbuf, err := cmd.Output()
 	stdout = strings.TrimSpace(string(outbuf))
 

+ 68 - 19
preparer.go

@@ -11,36 +11,59 @@ import (
 	"github.com/Jguer/yay/v11/pkg/db"
 	"github.com/Jguer/yay/v11/pkg/dep"
 	"github.com/Jguer/yay/v11/pkg/download"
+	"github.com/Jguer/yay/v11/pkg/menus"
 	"github.com/Jguer/yay/v11/pkg/settings"
 	"github.com/Jguer/yay/v11/pkg/settings/exe"
 	"github.com/Jguer/yay/v11/pkg/text"
 
+	gosrc "github.com/Morganamilo/go-srcinfo"
 	mapset "github.com/deckarep/golang-set/v2"
 	"github.com/leonelquinteros/gotext"
 )
 
+type PostDownloadHookFunc func(ctx context.Context, config *settings.Configuration, w io.Writer, pkgbuildDirsByBase map[string]string) error
+
 type Preparer struct {
-	dbExecutor db.Executor
-	cmdBuilder exe.ICmdBuilder
-	config     *settings.Configuration
+	dbExecutor        db.Executor
+	cmdBuilder        exe.ICmdBuilder
+	config            *settings.Configuration
+	postDownloadHooks []PostDownloadHookFunc
 
 	makeDeps []string
 }
 
+func NewPreparer(dbExecutor db.Executor, cmdBuilder exe.ICmdBuilder, config *settings.Configuration) *Preparer {
+	preper := &Preparer{
+		dbExecutor:        dbExecutor,
+		cmdBuilder:        cmdBuilder,
+		config:            config,
+		postDownloadHooks: []PostDownloadHookFunc{},
+	}
+
+	if config.CleanMenu {
+		preper.postDownloadHooks = append(preper.postDownloadHooks, menus.CleanFn)
+	}
+
+	if config.DiffMenu {
+		preper.postDownloadHooks = append(preper.postDownloadHooks, menus.DiffFn)
+	}
+
+	if config.EditMenu {
+		preper.postDownloadHooks = append(preper.postDownloadHooks, menus.EditFn)
+	}
+
+	return preper
+}
+
 func (preper *Preparer) ShouldCleanAURDirs(pkgBuildDirs map[string]string) PostInstallHookFunc {
 	if !preper.config.CleanAfter {
 		return nil
 	}
 
-	dirs := make([]string, 0, len(pkgBuildDirs))
-	for _, dir := range pkgBuildDirs {
-		dirs = append(dirs, dir)
-	}
-
-	text.Debugln("added post install hook to clean up AUR dirs", dirs)
+	text.Debugln("added post install hook to clean up AUR dirs", pkgBuildDirs)
 
 	return func(ctx context.Context) error {
-		cleanAfter(ctx, preper.config.Runtime.CmdBuilder, dirs)
+		cleanAfter(ctx, preper.config.Runtime.CmdBuilder, pkgBuildDirs)
 		return nil
 	}
 }
@@ -109,30 +132,56 @@ func (preper *Preparer) Present(w io.Writer, targets []map[string]*dep.InstallIn
 }
 
 func (preper *Preparer) PrepareWorkspace(ctx context.Context, targets []map[string]*dep.InstallInfo) (map[string]string, error) {
-	aurBases := mapset.NewThreadUnsafeSet[string]()
-	pkgBuildDirs := make(map[string]string, 0)
+	aurBasesToClone := mapset.NewThreadUnsafeSet[string]()
+	pkgBuildDirsByBase := make(map[string]string, len(targets))
 
 	for _, layer := range targets {
-		for pkgName, info := range layer {
+		for _, info := range layer {
 			if info.Source == dep.AUR {
 				pkgBase := *info.AURBase
-				aurBases.Add(pkgBase)
-				pkgBuildDirs[pkgName] = filepath.Join(config.BuildDir, pkgBase)
+				pkgBuildDir := filepath.Join(preper.config.BuildDir, pkgBase)
+				if preper.needToCloneAURBase(info, pkgBuildDir) {
+					aurBasesToClone.Add(pkgBase)
+				}
+				pkgBuildDirsByBase[pkgBase] = pkgBuildDir
 			} else if info.Source == dep.SrcInfo {
-				pkgBuildDirs[pkgName] = *info.SrcinfoPath
+				pkgBase := *info.AURBase
+				pkgBuildDirsByBase[pkgBase] = *info.SrcinfoPath
 			}
 		}
 	}
 
 	if _, errA := download.AURPKGBUILDRepos(ctx,
-		preper.cmdBuilder, aurBases.ToSlice(), config.AURURL, config.BuildDir, false); errA != nil {
+		preper.cmdBuilder, aurBasesToClone.ToSlice(),
+		config.AURURL, config.BuildDir, false); errA != nil {
 		return nil, errA
 	}
 
 	if errP := downloadPKGBUILDSourceFanout(ctx, config.Runtime.CmdBuilder,
-		pkgBuildDirs, false, config.MaxConcurrentDownloads); errP != nil {
+		pkgBuildDirsByBase, false, config.MaxConcurrentDownloads); errP != nil {
 		text.Errorln(errP)
 	}
 
-	return pkgBuildDirs, nil
+	for _, hookFn := range preper.postDownloadHooks {
+		if err := hookFn(ctx, preper.config, os.Stdout, pkgBuildDirsByBase); err != nil {
+			return nil, err
+		}
+	}
+
+	return pkgBuildDirsByBase, nil
+}
+
+func (preper *Preparer) needToCloneAURBase(installInfo *dep.InstallInfo, pkgbuildDir string) bool {
+	if preper.config.ReDownload == "all" {
+		return true
+	}
+
+	srcinfoFile := filepath.Join(pkgbuildDir, ".SRCINFO")
+	if pkgbuild, err := gosrc.ParseFile(srcinfoFile); err == nil {
+		if db.VerCmp(pkgbuild.Version(), installInfo.Version) >= 0 {
+			return false
+		}
+	}
+
+	return true
 }

+ 3 - 10
print.go

@@ -73,11 +73,7 @@ func biggestPackages(dbExecutor db.Executor) {
 func localStatistics(ctx context.Context, dbExecutor db.Executor) error {
 	info := statistics(dbExecutor)
 
-	_, remoteNames, err := query.GetPackageNamesBySource(dbExecutor)
-	if err != nil {
-		return err
-	}
-
+	remoteNames := dbExecutor.InstalledRemotePackageNames()
 	text.Infoln(gotext.Get("Yay version v%s", yayVersion))
 	fmt.Println(text.Bold(text.Cyan("===========================================")))
 	text.Infoln(gotext.Get("Total installed packages: %s", text.Cyan(strconv.Itoa(info.Totaln))))
@@ -124,11 +120,8 @@ func printUpdateList(ctx context.Context, cmdArgs *parser.Arguments,
 	old := os.Stdout // keep backup of the real stdout
 	os.Stdout = nil
 
-	localNames, remoteNames, err := query.GetPackageNamesBySource(dbExecutor)
-	if err != nil {
-		os.Stdout = old
-		return err
-	}
+	remoteNames := dbExecutor.InstalledRemotePackageNames()
+	localNames := dbExecutor.InstalledSyncPackageNames()
 
 	aurUp, repoUp, err := upList(ctx, nil, warnings, dbExecutor, enableDowngrade, filter)
 	os.Stdout = old // restoring the real stdout

+ 5 - 5
sync.go

@@ -52,11 +52,7 @@ func syncInstall(ctx context.Context,
 
 	topoSorted := graph.TopoSortedLayerMap()
 
-	preparer := &Preparer{
-		dbExecutor: dbExecutor,
-		cmdBuilder: config.Runtime.CmdBuilder,
-		config:     config,
-	}
+	preparer := NewPreparer(dbExecutor, config.Runtime.CmdBuilder, config)
 	installer := &Installer{dbExecutor: dbExecutor}
 
 	if errP := preparer.Present(os.Stdout, topoSorted); errP != nil {
@@ -73,6 +69,10 @@ func syncInstall(ctx context.Context,
 		return err
 	}
 
+	if cleanAURDirsFunc := preparer.ShouldCleanAURDirs(pkgBuildDirs); cleanAURDirsFunc != nil {
+		installer.AddPostInstallHook(cleanAURDirsFunc)
+	}
+
 	err = installer.Install(ctx, cmdArgs, topoSorted, pkgBuildDirs)
 	if err != nil {
 		if errHook := installer.RunPostInstallHooks(ctx); errHook != nil {

+ 2 - 1
upgrade.go

@@ -41,7 +41,8 @@ func upList(ctx context.Context, aurCache *metadata.Client,
 	warnings *query.AURWarnings, dbExecutor db.Executor, enableDowngrade bool,
 	filter upgrade.Filter,
 ) (aurUp, repoUp upgrade.UpSlice, err error) {
-	remote, remoteNames := query.GetRemotePackages(dbExecutor)
+	remote := dbExecutor.InstalledRemotePackages()
+	remoteNames := dbExecutor.InstalledRemotePackageNames()
 
 	var (
 		wg        sync.WaitGroup

+ 1 - 5
vcs.go

@@ -23,11 +23,7 @@ func createDevelDB(ctx context.Context, config *settings.Configuration, dbExecut
 		wg  sync.WaitGroup
 	)
 
-	_, remoteNames, err := query.GetPackageNamesBySource(dbExecutor)
-	if err != nil {
-		return err
-	}
-
+	remoteNames := dbExecutor.InstalledRemotePackageNames()
 	info, err := query.AURInfoPrint(ctx, config.Runtime.AURClient, remoteNames, config.RequestSplitN)
 	if err != nil {
 		return err