diff --git a/Makefile b/Makefile
index ff8412b..ae74c6f 100644
--- a/Makefile
+++ b/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 ./...
\ No newline at end of file
diff --git a/assets/coverage-badge.svg b/assets/coverage-badge.svg
index 4d357e3..776bb24 100644
--- a/assets/coverage-badge.svg
+++ b/assets/coverage-badge.svg
@@ -11,7 +11,7 @@
coverage
coverage
- 17.2%
- 17.2%
+ 17.1%
+ 17.1%
diff --git a/e2e-tests/common_test.go b/e2e-tests/common_test.go
index fd3b893..88424c3 100644
--- a/e2e-tests/common_test.go
+++ b/e2e-tests/common_test.go
@@ -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,
diff --git a/e2e-tests/issue_74_upgradable_test.go b/e2e-tests/issue_74_upgradable_test.go
new file mode 100644
index 0000000..e04a825
--- /dev/null
+++ b/e2e-tests/issue_74_upgradable_test.go
@@ -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 .
+
+//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")
+ },
+ )
+}
diff --git a/internal/translations/default.pot b/internal/translations/default.pot
index 0b23dfc..c39fb78 100644
--- a/internal/translations/default.pot
+++ b/internal/translations/default.pot
@@ -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 ""
diff --git a/internal/translations/po/ru/default.po b/internal/translations/po/ru/default.po
index 2c9d802..9d7fc9c 100644
--- a/internal/translations/po/ru/default.po
+++ b/internal/translations/po/ru/default.po
@@ -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 \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 "Обновить все установленные пакеты"
diff --git a/list.go b/list.go
index 117a7bc..afad318 100644
--- a/list.go
+++ b/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
diff --git a/upgrade.go b/upgrade.go
index 7b148aa..6316692 100644
--- a/upgrade.go
+++ b/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,
+ })
}
}