add --upgradable option for list
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Pre-commit / pre-commit (pull_request) Successful in 1m36s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Pre-commit / pre-commit (pull_request) Successful in 1m36s
				
			This commit is contained in:
		
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
									
									
									
									
								
							@@ -76,7 +76,9 @@ test-coverage:
 | 
			
		||||
update-deps-cve:
 | 
			
		||||
	bash scripts/update-deps-cve.sh
 | 
			
		||||
 | 
			
		||||
e2e-test: clean build
 | 
			
		||||
prepare-for-e2e-test: clean build
 | 
			
		||||
	rm -f ./e2e-tests/alr
 | 
			
		||||
	cp alr e2e-tests
 | 
			
		||||
 | 
			
		||||
e2e-test: prepare-for-e2e-test
 | 
			
		||||
	go test -tags=e2e ./...
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
    <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
 | 
			
		||||
        <text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
 | 
			
		||||
        <text x="33.5" y="14">coverage</text>
 | 
			
		||||
        <text x="86" y="15" fill="#010101" fill-opacity=".3">17.2%</text>
 | 
			
		||||
        <text x="86" y="14">17.2%</text>
 | 
			
		||||
        <text x="86" y="15" fill="#010101" fill-opacity=".3">17.1%</text>
 | 
			
		||||
        <text x="86" y="14">17.1%</text>
 | 
			
		||||
    </g>
 | 
			
		||||
</svg>
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 926 B  | 
@@ -175,6 +175,11 @@ func dockerMultipleRun(t *testing.T, name string, ids []string, f func(t *testin
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func simpleExec(t *testing.T, r e2e.Runnable, cmd string, args ...string) {
 | 
			
		||||
	err := r.Exec(e2e.NewCommand(cmd, args...))
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runTestCommands(t *testing.T, r e2e.Runnable, timeout time.Duration, expects []expect.Batcher) {
 | 
			
		||||
	exp, _, err, _ := e2eSpawn(
 | 
			
		||||
		r,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										50
									
								
								e2e-tests/issue_74_upgradable_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								e2e-tests/issue_74_upgradable_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
// ALR - Any Linux Repository
 | 
			
		||||
// Copyright (C) 2025 The ALR Authors
 | 
			
		||||
//
 | 
			
		||||
// This program is free software: you can redistribute it and/or modify
 | 
			
		||||
// it under the terms of the GNU General Public License as published by
 | 
			
		||||
// the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
// (at your option) any later version.
 | 
			
		||||
//
 | 
			
		||||
// This program is distributed in the hope that it will be useful,
 | 
			
		||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
// GNU General Public License for more details.
 | 
			
		||||
//
 | 
			
		||||
// You should have received a copy of the GNU General Public License
 | 
			
		||||
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
//go:build e2e
 | 
			
		||||
 | 
			
		||||
package e2etests_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/efficientgo/e2e"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestE2EIssue74Upgradable(t *testing.T) {
 | 
			
		||||
	dockerMultipleRun(
 | 
			
		||||
		t,
 | 
			
		||||
		"issue-74-upgradable",
 | 
			
		||||
		COMMON_SYSTEMS,
 | 
			
		||||
		func(t *testing.T, r e2e.Runnable) {
 | 
			
		||||
			simpleExec(t, r, "sudo",
 | 
			
		||||
				"alr",
 | 
			
		||||
				"addrepo",
 | 
			
		||||
				"--name",
 | 
			
		||||
				"alr-repo",
 | 
			
		||||
				"--url",
 | 
			
		||||
				REPO_FOR_E2E_TESTS,
 | 
			
		||||
			)
 | 
			
		||||
			simpleExec(t, r, "sudo", "sh", "-c", "sed -i 's/ref = .*/ref = \"bd26236cd7\"/' /etc/alr/alr.toml")
 | 
			
		||||
			simpleExec(t, r, "alr", "ref")
 | 
			
		||||
			simpleExec(t, r, "sudo", "alr", "in", "bar-pkg")
 | 
			
		||||
			simpleExec(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1")
 | 
			
		||||
			simpleExec(t, r, "sudo", "sh", "-c", "sed -i 's/ref = .*/ref = \"d9a3541561\"/' /etc/alr/alr.toml")
 | 
			
		||||
			simpleExec(t, r, "sudo", "alr", "ref")
 | 
			
		||||
			simpleExec(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 1 || exit 1")
 | 
			
		||||
		},
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
@@ -327,10 +327,30 @@ msgstr ""
 | 
			
		||||
msgid "You need to be root to perform this action"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: list.go:41
 | 
			
		||||
#: list.go:43
 | 
			
		||||
msgid "List ALR repo packages"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: list.go:57
 | 
			
		||||
msgid "Format output using a Go template"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: list.go:89
 | 
			
		||||
msgid "Error getting packages for upgrade"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: list.go:92
 | 
			
		||||
msgid "No packages for upgrade"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: list.go:102 list.go:187
 | 
			
		||||
msgid "Error parsing format template"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: list.go:108 list.go:191
 | 
			
		||||
msgid "Error executing template"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: main.go:45
 | 
			
		||||
msgid "Print the current ALR version and exit"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -495,22 +515,10 @@ msgstr ""
 | 
			
		||||
msgid "Search by provides"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: search.go:71
 | 
			
		||||
msgid "Format output using a Go template"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: search.go:130
 | 
			
		||||
msgid "Error while executing search"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: search.go:138
 | 
			
		||||
msgid "Error parsing format template"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: search.go:153
 | 
			
		||||
msgid "Error executing template"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: upgrade.go:47
 | 
			
		||||
msgid "Upgrade all installed packages"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 
 | 
			
		||||
@@ -5,15 +5,15 @@
 | 
			
		||||
msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: unnamed project\n"
 | 
			
		||||
"PO-Revision-Date: 2025-04-27 18:27+0300\n"
 | 
			
		||||
"PO-Revision-Date: 2025-05-13 23:24+0300\n"
 | 
			
		||||
"Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n"
 | 
			
		||||
"Language-Team: Russian\n"
 | 
			
		||||
"Language: ru\n"
 | 
			
		||||
"MIME-Version: 1.0\n"
 | 
			
		||||
"Content-Type: text/plain; charset=UTF-8\n"
 | 
			
		||||
"Content-Transfer-Encoding: 8bit\n"
 | 
			
		||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
 | 
			
		||||
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
 | 
			
		||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
 | 
			
		||||
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
 | 
			
		||||
"X-Generator: Gtranslator 48.0\n"
 | 
			
		||||
 | 
			
		||||
#: build.go:42
 | 
			
		||||
@@ -335,10 +335,30 @@ msgstr "Вы должны быть членом %s чтобы выполнить
 | 
			
		||||
msgid "You need to be root to perform this action"
 | 
			
		||||
msgstr "Вы должны быть root чтобы выполнить это"
 | 
			
		||||
 | 
			
		||||
#: list.go:41
 | 
			
		||||
#: list.go:43
 | 
			
		||||
msgid "List ALR repo packages"
 | 
			
		||||
msgstr "Список пакетов репозитория ALR"
 | 
			
		||||
 | 
			
		||||
#: list.go:57
 | 
			
		||||
msgid "Format output using a Go template"
 | 
			
		||||
msgstr "Формат выходных данных с использованием шаблона Go"
 | 
			
		||||
 | 
			
		||||
#: list.go:89
 | 
			
		||||
msgid "Error getting packages for upgrade"
 | 
			
		||||
msgstr "Ошибка при получении пакетов для обновления"
 | 
			
		||||
 | 
			
		||||
#: list.go:92
 | 
			
		||||
msgid "No packages for upgrade"
 | 
			
		||||
msgstr "Нет пакетов к обновлению"
 | 
			
		||||
 | 
			
		||||
#: list.go:102 list.go:187
 | 
			
		||||
msgid "Error parsing format template"
 | 
			
		||||
msgstr "Ошибка при разборе шаблона"
 | 
			
		||||
 | 
			
		||||
#: list.go:108 list.go:191
 | 
			
		||||
msgid "Error executing template"
 | 
			
		||||
msgstr "Ошибка при выполнении шаблона"
 | 
			
		||||
 | 
			
		||||
#: main.go:45
 | 
			
		||||
msgid "Print the current ALR version and exit"
 | 
			
		||||
msgstr "Показать текущую версию ALR и выйти"
 | 
			
		||||
@@ -509,22 +529,10 @@ msgstr "Искать по репозиторию"
 | 
			
		||||
msgid "Search by provides"
 | 
			
		||||
msgstr "Иcкать по provides"
 | 
			
		||||
 | 
			
		||||
#: search.go:71
 | 
			
		||||
msgid "Format output using a Go template"
 | 
			
		||||
msgstr "Формат выходных данных с использованием шаблона Go"
 | 
			
		||||
 | 
			
		||||
#: search.go:130
 | 
			
		||||
msgid "Error while executing search"
 | 
			
		||||
msgstr "Ошибка при выполнении поиска"
 | 
			
		||||
 | 
			
		||||
#: search.go:138
 | 
			
		||||
msgid "Error parsing format template"
 | 
			
		||||
msgstr "Ошибка при разборе шаблона"
 | 
			
		||||
 | 
			
		||||
#: search.go:153
 | 
			
		||||
msgid "Error executing template"
 | 
			
		||||
msgstr "Ошибка при выполнении шаблона"
 | 
			
		||||
 | 
			
		||||
#: upgrade.go:47
 | 
			
		||||
msgid "Upgrade all installed packages"
 | 
			
		||||
msgstr "Обновить все установленные пакеты"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										72
									
								
								list.go
									
									
									
									
									
								
							
							
						
						
									
										72
									
								
								list.go
									
									
									
									
									
								
							@@ -22,10 +22,12 @@ package main
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log/slog"
 | 
			
		||||
	"os"
 | 
			
		||||
	"slices"
 | 
			
		||||
	"text/template"
 | 
			
		||||
 | 
			
		||||
	"github.com/leonelquinteros/gotext"
 | 
			
		||||
	"github.com/urfave/cli/v2"
 | 
			
		||||
	"golang.org/x/exp/slices"
 | 
			
		||||
 | 
			
		||||
	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
 | 
			
		||||
	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
 | 
			
		||||
@@ -45,6 +47,15 @@ func ListCmd() *cli.Command {
 | 
			
		||||
				Name:    "installed",
 | 
			
		||||
				Aliases: []string{"I"},
 | 
			
		||||
			},
 | 
			
		||||
			&cli.BoolFlag{
 | 
			
		||||
				Name:    "upgradable",
 | 
			
		||||
				Aliases: []string{"U"},
 | 
			
		||||
			},
 | 
			
		||||
			&cli.StringFlag{
 | 
			
		||||
				Name:    "format",
 | 
			
		||||
				Aliases: []string{"f"},
 | 
			
		||||
				Usage:   gotext.Get("Format output using a Go template"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Action: func(c *cli.Context) error {
 | 
			
		||||
			if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
 | 
			
		||||
@@ -57,8 +68,10 @@ func ListCmd() *cli.Command {
 | 
			
		||||
				New(ctx).
 | 
			
		||||
				WithConfig().
 | 
			
		||||
				WithDB().
 | 
			
		||||
				WithManager().
 | 
			
		||||
				// autoPull only
 | 
			
		||||
				WithRepos().
 | 
			
		||||
				WithDistroInfo().
 | 
			
		||||
				Build()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
@@ -67,6 +80,39 @@ func ListCmd() *cli.Command {
 | 
			
		||||
 | 
			
		||||
			cfg := deps.Cfg
 | 
			
		||||
			db := deps.DB
 | 
			
		||||
			mgr := deps.Manager
 | 
			
		||||
			info := deps.Info
 | 
			
		||||
 | 
			
		||||
			if c.Bool("upgradable") {
 | 
			
		||||
				updates, err := checkForUpdates(ctx, mgr, db, info)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return cliutils.FormatCliExit(gotext.Get("Error getting packages for upgrade"), err)
 | 
			
		||||
				}
 | 
			
		||||
				if len(updates) == 0 {
 | 
			
		||||
					slog.Info(gotext.Get("No packages for upgrade"))
 | 
			
		||||
					return nil
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				format := c.String("format")
 | 
			
		||||
				if format == "" {
 | 
			
		||||
					format = "{{.Package.Repository}}/{{.Package.Name}} {{.FromVersion}} -> {{.ToVersion}}\n"
 | 
			
		||||
				}
 | 
			
		||||
				tmpl, err := template.New("format").Parse(format)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return cliutils.FormatCliExit(gotext.Get("Error parsing format template"), err)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				for _, updateInfo := range updates {
 | 
			
		||||
					err = tmpl.Execute(os.Stdout, updateInfo)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						return cliutils.FormatCliExit(gotext.Get("Error executing template"), err)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// TODO: refactor code below
 | 
			
		||||
 | 
			
		||||
			where := "true"
 | 
			
		||||
			args := []any(nil)
 | 
			
		||||
@@ -115,17 +161,35 @@ func ListCmd() *cli.Command {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				version := pkg.Version
 | 
			
		||||
				type packageInfo struct {
 | 
			
		||||
					Package *database.Package
 | 
			
		||||
					Version string
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				pkgInfo := &packageInfo{}
 | 
			
		||||
				pkgInfo.Package = &pkg
 | 
			
		||||
				pkgInfo.Version = pkg.Version
 | 
			
		||||
				if c.Bool("installed") {
 | 
			
		||||
					instVersion, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)]
 | 
			
		||||
					if !ok {
 | 
			
		||||
						continue
 | 
			
		||||
					} else {
 | 
			
		||||
						version = instVersion
 | 
			
		||||
						pkgInfo.Version = instVersion
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				fmt.Printf("%s/%s %s\n", pkg.Repository, pkg.Name, version)
 | 
			
		||||
				format := c.String("format")
 | 
			
		||||
				if format == "" {
 | 
			
		||||
					format = "{{.Package.Repository}}/{{.Package.Name}} {{.Version}}\n"
 | 
			
		||||
				}
 | 
			
		||||
				tmpl, err := template.New("format").Parse(format)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return cliutils.FormatCliExit(gotext.Get("Error parsing format template"), err)
 | 
			
		||||
				}
 | 
			
		||||
				err = tmpl.Execute(os.Stdout, pkg)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return cliutils.FormatCliExit(gotext.Get("Error executing template"), err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										32
									
								
								upgrade.go
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								upgrade.go
									
									
									
									
									
								
							@@ -116,7 +116,7 @@ func UpgradeCmd() *cli.Command {
 | 
			
		||||
						Info:       deps.Info,
 | 
			
		||||
						PkgFormat_: build.GetPkgFormat(deps.Manager),
 | 
			
		||||
					},
 | 
			
		||||
					updates,
 | 
			
		||||
					mapUptatesInfoToPackages(updates),
 | 
			
		||||
				)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return cliutils.FormatCliExit(gotext.Get("Error checking for updates"), err)
 | 
			
		||||
@@ -130,12 +130,27 @@ func UpgradeCmd() *cli.Command {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mapUptatesInfoToPackages(updates []UpdateInfo) []database.Package {
 | 
			
		||||
	var pkgs []database.Package
 | 
			
		||||
	for _, info := range updates {
 | 
			
		||||
		pkgs = append(pkgs, *info.Package)
 | 
			
		||||
	}
 | 
			
		||||
	return pkgs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UpdateInfo struct {
 | 
			
		||||
	Package *database.Package
 | 
			
		||||
 | 
			
		||||
	FromVersion string
 | 
			
		||||
	ToVersion   string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkForUpdates(
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
	mgr manager.Manager,
 | 
			
		||||
	db *database.Database,
 | 
			
		||||
	info *distro.OSRelease,
 | 
			
		||||
) ([]database.Package, error) {
 | 
			
		||||
) ([]UpdateInfo, error) {
 | 
			
		||||
	installed, err := mgr.ListInstalled(nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
@@ -145,7 +160,7 @@ func checkForUpdates(
 | 
			
		||||
 | 
			
		||||
	s := search.New(db)
 | 
			
		||||
 | 
			
		||||
	var out []database.Package
 | 
			
		||||
	var out []UpdateInfo
 | 
			
		||||
	for _, pkgName := range pkgNames {
 | 
			
		||||
		matches := build.RegexpALRPackageName.FindStringSubmatch(pkgName)
 | 
			
		||||
		if matches != nil {
 | 
			
		||||
@@ -179,10 +194,13 @@ func checkForUpdates(
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			c := vercmp.Compare(repoVer, installed[pkgName])
 | 
			
		||||
			if c == 0 || c == -1 {
 | 
			
		||||
				continue
 | 
			
		||||
			} else if c == 1 {
 | 
			
		||||
				out = append(out, pkg)
 | 
			
		||||
 | 
			
		||||
			if c == 1 {
 | 
			
		||||
				out = append(out, UpdateInfo{
 | 
			
		||||
					Package:     &pkg,
 | 
			
		||||
					FromVersion: installed[pkgName],
 | 
			
		||||
					ToVersion:   repoVer,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user