fix(show): star marker, aligned gutter and smarter AUR merge

N?? / show marks the upstream candidate with "*" instead of ">" (the
prompt, which read as a command in logs), and every line but the note
gets a two-character gutter: "  " for the installed record, "* " for the
candidate, so keys and colons line up. The plain native fallback (zypper,
unmergeable records) and the not-installed candidate use the same gutter.

pacman records (-Qi with -Si or the AUR helper's -Sia) are now compared
the way they are meant:
- values unwrapped (continuation lines joined, runs of blanks as one):
  yay wraps Depends On / Optional Deps at 80 columns even without a
  terminal unless COLUMNS=0, which the helper now gets for -Qua and -Sia
- Depends On, Provides, Conflicts With, Make/Check Deps and the other
  lists as sets of exact tokens in any order (a constraint is a change)
- Optional Deps by package names ("name: description [installed]" in
  -Qi, bare names from the AUR)
- a differing value is one line like -Qi, Optional Deps one entry per
  line; candidate-only fields sit next to their counterpart (Repository
  after Version, AUR URL after URL, Maintainer after Packager, ...)

Verified on recorded claude-desktop records (paru 2.1.0, yay 13.0.1, the
installed 2.9939.4-1 from the AUR's own .SRCINFO); discovery/pacman.md
section 15.2.
This commit is contained in:
bryanpedini committed 2026-10-10 16:01:51 +02:00
1 parent def537af58
commit 3af159e09a
16 files changed
+680 -288

No files matched your search

+53 -40
View File
@@ -137,7 +137,7 @@ listed prints an error, runs nothing, and the prompt comes back.
|-------|--------| |-------|--------|
| empty line | proceed: run the native upgrade with the current exclusions | | empty line | proceed: run the native upgrade with the current exclusions |
| `N?` | what is it: the one-line description, `reason:` how it got installed (`explicitly installed`, `installed as a dependency`, `unknown` when the package manager cannot tell, `not installed` for a new dependency) and `required by:` the packages that depend on it, or `nothing`. That list covers installed packages only | | `N?` | what is it: the one-line description, `reason:` how it got installed (`explicitly installed`, `installed as a dependency`, `unknown` when the package manager cannot tell, `not installed` for a new dependency) and `required by:` the packages that depend on it, or `nothing`. That list covers installed packages only |
| `N??` | full native info: the installed record, with the upstream candidate merged in as lines starting with `>` (see below). zypper shows `zypper info --requires` as it is | | `N??` | full native info: the installed record, with the upstream candidate merged in as lines starting with `*` (see below); every line has a two-character gutter (` ` installed, `* ` upstream). zypper shows `zypper info --requires` as it is |
| `N-` | exclude from this run (toggle; `N-` again re-includes); shown with a `-` marker | | `N-` | exclude from this run (toggle; `N-` again re-includes); shown with a `-` marker |
| `Nx` | purge now, with its now-unneeded dependencies: the purge is simulated first and the prompt lists everything it removes (`Purge su-dummy-a now? This also removes: su-dummy-b [y/N]`); the answer is read from the same stdin; the rows of every removed package leave the list. `x` refuses when other installed packages depend on the target, like `pacman -Rns`; purge the dependents first (`not purged: su-dummy-b is required by: su-dummy-a - purge the dependents first`). Rows with old version `-` (new dependencies, not installed) cannot be purged | | `Nx` | purge now, with its now-unneeded dependencies: the purge is simulated first and the prompt lists everything it removes (`Purge su-dummy-a now? This also removes: su-dummy-b [y/N]`); the answer is read from the same stdin; the rows of every removed package leave the list. `x` refuses when other installed packages depend on the target, like `pacman -Rns`; purge the dependents first (`not purged: su-dummy-b is required by: su-dummy-a - purge the dependents first`). Rows with old version `-` (new dependencies, not installed) cannot be purged |
| `N+` | trust forever: write it and its installed dependency closure to the statefile; the row leaves the list. Rows this hides are re-included if they were excluded (a trusted package is upgraded) | | `N+` | trust forever: write it and its installed dependency closure to the statefile; the row leaves the list. Rows this hides are re-included if they were excluded (a trusted package is upgraded) |
@@ -153,43 +153,52 @@ session ends by itself (exit 0).
`N??` (and `show PKG`) prints the installed record in the package manager's own layout `N??` (and `show PKG`) prints the installed record in the package manager's own layout
(`dpkg-query -s`, `pacman -Qi`, `dnf info --installed`), preceded by one `#` line saying how to read (`dpkg-query -s`, `pacman -Qi`, `dnf info --installed`), preceded by one `#` line saying how to read
it. Right after each field whose value differs in the upstream candidate (`apt-cache show`, it. Every other line starts with a two-character gutter, so keys and colons line up: two spaces
`pacman -Si` from the repository pacman would use, `dnf info --upgrades`) comes the candidate's line, for the installed record, `* ` for the upstream candidate. Right after each field whose value
prefixed with `>`; fields only the candidate has (download size, checksums, repository) follow at the differs in the upstream candidate (`apt-cache show`, `pacman -Si` from the repository pacman would
end the same way, and fields only the installed record has (install reason, conffiles, use, `dnf info --upgrades`) comes the candidate's line, marked `* `; fields only the installed record
`Required By`) stay as they are. Identical fields are printed once; multi-line values (descriptions, pacman has (install reason, conffiles, `Required By`) stay as they are, and identical fields are printed
`Optional Deps`, apt continuation lines) are compared as blocks and, when they differ, every line of once. Fields only the candidate has (download size, checksums, repository) follow at the end the same
the candidate's block gets the `>`. A package that is not installed shows the candidate alone. When way - on pacman next to their installed counterpart instead (`Repository` after `Version`,
the two records cannot be paired (zypper, whose `rpm -qi` and `zypper info` share almost no keys; no `Download Size` after `Installed Size`; for the AUR `AUR URL` after `URL`, `Make Deps`/`Check Deps`
candidate; several installed architectures) the plain native output is shown. On Debian 12, with after `Optional Deps`, `Maintainer` after `Packager`, `Last Modified` and `First Submitted` after
`su-dummy-b` 1.0 installed and 2.0 available: `Build Date`, the rest at the end). Multi-line values (descriptions, apt continuation lines) are
compared as blocks and, when they differ, every line of the candidate's block gets the `* `. On pacman
values are compared unwrapped (the AUR helpers wrap long lists at the terminal width), lists such as
`Depends On` as sets of tokens in any order, `Optional Deps` by package names only (the installed
record says `name: description [installed]`, the AUR bare names); a differing value is printed on one
line like `pacman -Qi` does, `Optional Deps` one entry per line. A package that is not installed shows
the candidate alone, every line marked `* `. When the two records cannot be paired (zypper, whose
`rpm -qi` and `zypper info` share almost no keys; no candidate; several installed architectures) the
plain native output is shown, in the same gutter. On Debian 12, with `su-dummy-b` 1.0 installed and
2.0 available:
``` ```
> 2?? > 2??
# lines starting with > come from the upstream candidate: a differing value, or a field the installed record lacks # lines starting with * come from the upstream candidate: a differing value, or a field the installed record lacks
Package: su-dummy-b Package: su-dummy-b
Status: install ok installed Status: install ok installed
Priority: optional Priority: optional
Section: misc Section: misc
Maintainer: Skeptical Updater Fixture <su-fixture@example.invalid> Maintainer: Skeptical Updater Fixture <su-fixture@example.invalid>
Architecture: all Architecture: all
Version: 1.0 Version: 1.0
> Version: 2.0 * Version: 2.0
Conffiles: Conffiles:
/etc/su-dummy/su-dummy-b.conf 607e2d3a119dfe832d937ce579ef1079 /etc/su-dummy/su-dummy-b.conf 607e2d3a119dfe832d937ce579ef1079
Description: dummy package su-dummy-b for skeptical-updater tests Description: dummy package su-dummy-b for skeptical-updater tests
This package only exists to exercise the package manager. This package only exists to exercise the package manager.
. .
It ships one data file and one conffile (version 1.0). It ships one data file and one conffile (version 1.0).
> Description: dummy package su-dummy-b for skeptical-updater tests * Description: dummy package su-dummy-b for skeptical-updater tests
> This package only exists to exercise the package manager. * This package only exists to exercise the package manager.
> . * .
> It ships one data file and one conffile (version 2.0). * It ships one data file and one conffile (version 2.0).
> Description-md5: 27e6732d8f16131f7512e1d4f1855cc3 * Description-md5: 27e6732d8f16131f7512e1d4f1855cc3
> Filename: ./su-dummy-b_2.0_all.deb * Filename: ./su-dummy-b_2.0_all.deb
> Size: 824 * Size: 824
> MD5sum: 62274690c5324af5419515e9f430226b * MD5sum: 62274690c5324af5419515e9f430226b
> SHA256: e1c87480693d98770356f1dc55616dc416f7e3c3f71dc3967e94dec77bb36c16 * SHA256: e1c87480693d98770356f1dc55616dc416f7e3c3f71dc3967e94dec77bb36c16
``` ```
Under the rows, `Note:` lines report pending work that is not a row: new dependencies or Under the rows, `Note:` lines report pending work that is not a row: new dependencies or
@@ -241,8 +250,9 @@ Upgrade finished.
AUR row (a package that moved from the AUR into a repository). AUR row (a package that moved from the AUR into a repository).
- **`N?`, `N+`, `Nx`** work as for any installed package, through pacman (`pacman -Qi`, - **`N?`, `N+`, `Nx`** work as for any installed package, through pacman (`pacman -Qi`,
the statefile with backend `pacman`, `pacman -Rns`). **`N??`** merges `pacman -Qi` with the the statefile with backend `pacman`, `pacman -Rns`). **`N??`** merges `pacman -Qi` with the
AUR record (`paru -Sia` / `yay -Sia`), `>` lines as usual: `> Version`, `> Repository : aur`, AUR record (`paru -Sia` / `yay -Sia`), `*` lines as usual: `* Version` followed by
`> Maintainer`, `> Votes`, ... (`show PKG` does the same for a package in no repository). `* Repository : aur`, `* AUR URL` under `URL`, `* Maintainer` under `Packager`, `* Votes`, ...
(`show PKG` does the same for a package in no repository).
- **`N-`** excludes an AUR row through the helper's `--ignore`. - **`N-`** excludes an AUR row through the helper's `--ignore`.
- **`<enter>`** upgrades the repository packages first, exactly as without the AUR (pre-check, - **`<enter>`** upgrades the repository packages first, exactly as without the AUR (pre-check,
the reviewed databases, `pacman -Su`), then, if AUR rows are left to upgrade, runs the reviewed databases, `pacman -Su`), then, if AUR rows are left to upgrade, runs
@@ -398,7 +408,9 @@ database install / `-Su` sequence and stale throwaway databases, statefile locki
the `mkdir` lock fallback (EPERM owner alive, no busy loop), an inaccessible state directory, the the `mkdir` lock fallback (EPERM owner alive, no busy loop), an inaccessible state directory, the
sudo re-exec of the file itself and its loop guard), the `N??` merge on recorded pacman, apt, dnf4 sudo re-exec of the file itself and its loop guard), the `N??` merge on recorded pacman, apt, dnf4
and dnf5 records (changed, identical, installed-only, candidate-only and multi-line fields, apt's and dnf5 records (changed, identical, installed-only, candidate-only and multi-line fields, apt's
short candidate descriptions, dnf5's padding, the fallbacks), the prompt on a real pty (`script`): short candidate descriptions, dnf5's padding, the fallbacks, the two-character gutter; on pacman the
unwrapped comparison, token sets, `Optional Deps` name sets and the placement of candidate-only
fields, on the recorded `claude-desktop` records of paru and of yay, which wraps), the prompt on a real pty (`script`):
cursor editing, Up/Down history recall, the stashed line, the bell at both ends, no history file, cursor editing, Up/Down history recall, the stashed line, the bell at both ends, no history file,
and the AUR (`tests/unit/aur.bats`, on recorded paru 2.1.0 and yay 13.0.1 output): helper detection and the AUR (`tests/unit/aur.bats`, on recorded paru 2.1.0 and yay 13.0.1 output): helper detection
and overrides, the user the helper runs as (`runuser -u` / `sudo -u` with `SUDO_USER`, unprivileged, and overrides, the user the helper runs as (`runuser -u` / `sudo -u` with `SUDO_USER`, unprivileged,
@@ -422,8 +434,9 @@ mirror state. `su-dummy-b` is recorded as installed as a dependency on every bac
`--asdeps`, `apt-mark auto`, `dnf mark remove`, zypper's `AutoInstalled`) and `su-dummy-a` as `--asdeps`, `apt-mark auto`, `dnf mark remove`, zypper's `AutoInstalled`) and `su-dummy-a` as
explicitly installed (dnf needs `dnf mark install`: it records plain `rpm` installs as explicitly installed (dnf needs `dnf mark install`: it records plain `rpm` installs as
`unknown`). Then `tests/e2e/*.bats` exercise the real package manager: banner, list, `N?` (the `unknown`). Then `tests/e2e/*.bats` exercise the real package manager: banner, list, `N?` (the
reason and `required by:` of both), `N??` (su-dummy-b's installed 1.0 record with `> Version`, and reason and `required by:` of both), `N??` (su-dummy-b's installed 1.0 record with `* Version`, and
`> Repository` on pacman and dnf; zypper's plain `zypper info`), `N+` and the statefile, `* Repository` on pacman and dnf, every line in the gutter; zypper's plain `zypper info`), `N+` and
the statefile,
`trust`/`untrust`/`refresh`, `trust`/`untrust`/`refresh`,
excluding `su-dummy-b` (with and without a pre-existing hold/lock/IgnorePkg/exclude on excluding `su-dummy-b` (with and without a pre-existing hold/lock/IgnorePkg/exclude on
`su-dummy-c`, which must survive byte for byte), trusting re-including an excluded `su-dummy-c`, which must survive byte for byte), trusting re-including an excluded
+29 -1
View File
@@ -713,6 +713,33 @@ Out Of Date : No
Submitted`, `Last Modified`, `Out Of Date` (yay: `Out-of-date`, plus `Keywords`). Submitted`, `Last Modified`, `Out Of Date` (yay: `Out-of-date`, plus `Keywords`).
* Not in the AUR: paru `error: package 'no-such-package-zzz' was not found`, yay ` -> No AUR package found for * Not in the AUR: paru `error: package 'no-such-package-zzz' was not found`, yay ` -> No AUR package found for
no-such-package-zzz`, exit 1. AUR unreachable: the `-Qua` errors above, exit 1. no-such-package-zzz`, exit 1. AUR unreachable: the `-Qua` errors above, exit 1.
* Optional dependencies are **bare names** in both (`Optional Deps : pipewire kdialog gnome-keyring kwallet`
for `google-chrome`, whose AUR entries carry descriptions such as `pipewire: WebRTC desktop sharing under
Wayland`): the descriptions are dropped. `-Qi` prints one `name: description [installed]` per line.
* **yay wraps** long values at 80 columns - `Depends On`, `Optional Deps`, continuation lines indented to the value
column (31) - whenever `COLUMNS` is unset or 80, also without any terminal (stdout and stdin not a tty);
`COLUMNS=0` turns it off. paru 2.1.0 printed one line per field in every case tried (no terminal; an 80-column
pty with stdout piped, `COLUMNS` unset, 0 or 80; stdout the pty itself with `COLUMNS=0`). Verified 2026-10-10
with `claude-desktop` 2.31226.1-1 (39 dependencies), `yay -Sia claude-desktop` without `COLUMNS=0`:
```
Depends On : alsa-lib at-spi2-core bubblewrap cairo dbus
expat gcc-libs glib2 glibc gtk3
...
xdg-desktop-portal xdg-utils qemu-system-x86
edk2-ovmf
Optional Deps : gnome-keyring kwallet libayatana-appindicator
xdg-desktop-portal-gtk xdg-desktop-portal-kde
qemu-system-aarch64 edk2-aarch64
```
(paru: `Depends On : alsa-lib at-spi2-core bubblewrap ... edk2-ovmf` on one line.) The installed side for
that comparison, `pacman -Qi claude-desktop` of 2.9939.4-1 (a stand-in package built from the AUR's own
`.SRCINFO` of that version, commit b7d5715, plus a dummy `kwallet` for `[installed]`): `Depends On` one line
without `bubblewrap`, `libpipewire`, `qemu-system-x86`, `edk2-ovmf`; `Optional Deps : gnome-keyring:
credential storage via Secret Service (GNOME)` / `kwallet: credential storage (KDE Plasma) [installed]` / ...
one per line. These records are the unit fixtures (`tests/unit/data/pacman/Qi.txt`,
`tests/unit/data/aur/Sia-{paru,yay}-claude-desktop.txt`).
### 15.3 `-Sua`: the AUR upgrade ### 15.3 `-Sua`: the AUR upgrade
@@ -739,5 +766,6 @@ Out Of Date : No
|------|---------| |------|---------|
| helper | `--aur-helper=paru\|yay\|none` / `--no-aur`, else `paru`, else `yay` | | helper | `--aur-helper=paru\|yay\|none` / `--no-aur`, else `paru`, else `yay` |
| list | `<helper> -Qua`: rows `name old new aur` (flag `aur`, `[ignored]` -> held); a name `pacman -Qu` lists wins; exit 1 + empty stderr = none, other failures: warn, no AUR rows | | list | `<helper> -Qua`: rows `name old new aur` (flag `aur`, `[ignored]` -> held); a name `pacman -Qu` lists wins; exit 1 + empty stderr = none, other failures: warn, no AUR rows |
| `N??` | `pacman -Qi` merged with `<helper> -Sia PKG` (section 14's merge) | | `N??` | `pacman -Qi` merged with `<helper> -Sia PKG` (section 14's merge; pacman mode: values unwrapped, lists as token sets, `Optional Deps` as name sets, AUR-only fields next to their counterparts) |
| `COLUMNS` | `COLUMNS=0` for `-Qua` and `-Sia` (yay's wrapping); the upgrade keeps the terminal's |
| upgrade | after the repository step: `<helper> -Sua [--noconfirm when stdin is not a terminal] [--ignore <excluded rows>]` | | upgrade | after the repository step: `<helper> -Sua [--noconfirm when stdin is not a terminal] [--ignore <excluded rows>]` |
+44 -13
View File
@@ -170,31 +170,62 @@ history by itself). readline draws the prompt and the line on stderr, as bash do
stdin keeps the plain `IFS= read -r` with the prompt on stdout and each line echoed after it, so stdin keeps the plain `IFS= read -r` with the prompt on stdout and each line echoed after it, so
piped runs and their logs are unchanged. piped runs and their logs are unchanged.
Full info (`N??`, `show PKG`). The first line is `# lines starting with > come from the upstream Full info (`N??`, `show PKG`). The first line is `# lines starting with * come from the upstream
candidate: a differing value, or a field the installed record lacks`; then the installed record in candidate: a differing value, or a field the installed record lacks` (no gutter); every other line
its native key/value layout, as the base. After each field whose candidate value differs, the starts with a two-character gutter, ` ` for the installed record and its continuation lines, `* `
candidate's line follows, prefixed with `> ` (same key formatting); fields only the candidate has for the candidate's lines and theirs, so keys and colons line up. Then the installed record in its
are appended at the end the same way; fields only the installed record has stay; identical fields native key/value layout, as the base. After each field whose candidate value differs, the
print once. A field starts on a line that begins with a non-blank character and holds a `:` (the candidate's line follows, marked `* ` (same key formatting); fields only the candidate has are
key is the text before it); lines beginning with a blank continue it, so multi-line values (pacman appended at the end the same way (pacman: placed next to their counterpart, below); fields only the
`Optional Deps`, dnf `Description`, RFC-822 continuation lines) are compared as blocks, every line installed record has stay; identical fields print once. A field starts on a line that begins with a
trimmed, and a differing candidate block gets `> ` on every line. `Description-xx` (apt Translation non-blank character and holds a `:` (the key is the text before it); lines beginning with a blank
continue it, so multi-line values (pacman `Optional Deps`, dnf `Description`, RFC-822 continuation
lines) are compared as blocks, every line trimmed, and a differing candidate block gets `* ` on every
line. `Description-xx` (apt Translation
files) pairs with `Description`; on apt a one-line candidate `Description` equal to the first files) pairs with `Description`; on apt a one-line candidate `Description` equal to the first
installed line counts as identical (the archives carry only the short description, the long text installed line counts as identical (the archives carry only the short description, the long text
is in the Translation files). When both records align their `:` in one column but in different is in the Translation files). When both records align their `:` in one column but in different
ones (dnf5: 16 installed, 15 available), the candidate lines are re-padded to the installed one's. ones (dnf5: 16 installed, 15 available; yay's AUR records: 30), the candidate lines are re-padded to
the installed one's.
pacman (`-Qi` with `-Si REPO/PKG` or `<helper> -Sia PKG`, `discovery/pacman.md` sections 14-15):
- values are compared unwrapped - continuation lines joined to their key on both sides, runs of
blanks as one blank - because the AUR helpers wrap long lists at the terminal width;
- `Groups`, `Provides`, `Depends On`, `Required By`, `Optional For`, `Conflicts With`, `Replaces`,
`Make Deps`, `Check Deps`, `Licenses`, `Keywords`: as sets of exact tokens, in any order (a version
constraint is part of its token, so a changed constraint is a difference);
- `Optional Deps`: as sets of package names - `: description` and ` [installed]` stripped (`-Qi`
prints `name: description [installed]` per line, the AUR records bare names separated by blanks);
- a differing candidate value is printed on one line, as `-Qi` prints every field (an unwrapped
helper value is joined with two blanks for the lists above, one otherwise; a one-line value stays
as it is); `Optional Deps`, one entry per line in `-Qi`, gets one entry per line under the value
column;
- candidate-only fields go next to their installed counterpart, in this order, the others at the end
in the candidate's order:
| candidate field | right after |
|-----------------|-------------|
| `Repository` | `Version` |
| `AUR URL` | `URL` |
| `Keywords` | `Groups` |
| `Make Deps`, `Check Deps` | `Optional Deps` |
| `Maintainer` | `Packager` |
| `Last Modified`, `First Submitted` | `Build Date` |
| `Download Size` | `Installed Size` |
| backend | installed record | upstream candidate | | backend | installed record | upstream candidate |
|---------|------------------|--------------------| |---------|------------------|--------------------|
| pacman | `pacman -Qi PKG` | `pacman -Si REPO/PKG`, REPO = the first repository `pacman -Sl` lists PKG in (as for the review list); a package in no repository: the AUR record, `<helper> -Sia PKG`, when a helper is in use (should it not merge, `pacman -Qi` is followed by the AUR record with every line marked `> `) | | pacman | `pacman -Qi PKG` | `pacman -Si REPO/PKG`, REPO = the first repository `pacman -Sl` lists PKG in (as for the review list); a package in no repository: the AUR record, `<helper> -Sia PKG`, when a helper is in use (should it not merge, `pacman -Qi` is followed by the AUR record with every line marked `* `) |
| apt | `dpkg-query -s PKG` (Status, Conffiles, always the long description; `apt-cache show PKG=<installed>` gives the archive's record when that version is still listed: no Status/Conffiles, short description only); a `Status` of `not-installed`/`config-files` = not installed | `apt-cache show --no-all-versions PKG` | | apt | `dpkg-query -s PKG` (Status, Conffiles, always the long description; `apt-cache show PKG=<installed>` gives the archive's record when that version is still listed: no Status/Conffiles, short description only); a `Status` of `not-installed`/`config-files` = not installed | `apt-cache show --no-all-versions PKG` |
| dnf | `dnf -q info --installed PKG` | `dnf -q info --upgrades PKG` (installed architecture only; `--available` also lists other architectures and, for an up-to-date package, other builds); not installed: `--available` | | dnf | `dnf -q info --installed PKG` | `dnf -q info --upgrades PKG` (installed architecture only; `--available` also lists other architectures and, for an up-to-date package, other builds); not installed: `--available` |
| zypper | - | - (`rpm -qi` and `zypper info` share almost no keys - Version vs version-release, Arch, byte sizes, source names, description layout - and zypper cannot print the installed record by itself: `zypper info -r @System` is refused) | | zypper | - | - (`rpm -qi` and `zypper info` share almost no keys - Version vs version-release, Arch, byte sizes, source names, description layout - and zypper cannot print the installed record by itself: `zypper info -r @System` is refused) |
A package that is not installed (a row whose old version is `-` is not asked) shows the candidate A package that is not installed (a row whose old version is `-` is not asked) shows the candidate
alone under `# not installed: this is the upstream candidate`. Anything else - no candidate (an alone, every line marked `* `, under `# not installed: lines starting with * come from the upstream
candidate (all of them)`. Anything else - no candidate (an
up-to-date dnf package, a local-only pacman package), not exactly one record on each side (two up-to-date dnf package, a local-only pacman package), not exactly one record on each side (two
installed architectures), zypper - prints `<be>_full_info` unchanged. `dnf info` headings installed architectures), zypper - prints `<be>_full_info` as it is (in the " " gutter). `dnf info` headings
("Installed Packages", ...) are dropped. Verified formats: `discovery/{pacman,apt,rpm}.md`. ("Installed Packages", ...) are dropped. Verified formats: `discovery/{pacman,apt,rpm}.md`.
Flags shown after a row: `[held]` = the package manager will not upgrade it on its own: on apt Flags shown after a row: `[held]` = the package manager will not upgrade it on its own: on apt
+97 -37
View File
@@ -112,7 +112,7 @@ interactive commands (one line, whitespace-separated tokens, N = number in brack
N? what is it: the description, 'reason:' (explicitly installed, installed as N? what is it: the description, 'reason:' (explicitly installed, installed as
a dependency, unknown or not installed) and 'required by:', the packages a dependency, unknown or not installed) and 'required by:', the packages
that depend on it - installed packages only that depend on it - installed packages only
N?? full native info: the installed record; lines starting with '>' come N?? full native info: the installed record; lines starting with '*' come
from the upstream candidate (differing values, fields it adds) from the upstream candidate (differing values, fields it adds)
N- exclude from this run (toggle); shown with a '-' marker N- exclude from this run (toggle); shown with a '-' marker
Nx purge it now with its now-unneeded dependencies (shown, asks y/N); refused Nx purge it now with its now-unneeded dependencies (shown, asks y/N); refused
@@ -137,7 +137,7 @@ session_help() {
cat <<'EOF' cat <<'EOF'
<enter> upgrade now, skipping the rows marked '-' <enter> upgrade now, skipping the rows marked '-'
N? what is it: description, install reason + installed packages that need it N? what is it: description, install reason + installed packages that need it
N?? full native info: installed record, '>' lines from the upstream candidate N?? full native info: installed record, '*' lines from the upstream candidate
N- exclude N from this run (toggle) N- exclude N from this run (toggle)
Nx purge N now: lists every package the purge removes, then asks y/N. Nx purge N now: lists every package the purge removes, then asks y/N.
x refuses when other installed packages depend on the target, like x refuses when other installed packages depend on the target, like
@@ -908,7 +908,7 @@ apt_describe() {
[[ -n $out ]] || out='(no description available)' [[ -n $out ]] || out='(no description available)'
printf '%s\n' "$out" printf '%s\n' "$out"
} }
apt_full_info() { apt-cache show --no-all-versions "$1" </dev/null; } apt_full_info() { apt-cache show --no-all-versions "$1" </dev/null | gutter; }
# apt_installed_record PKG: dpkg's own record (`dpkg-query -s`: richer than `apt-cache show # apt_installed_record PKG: dpkg's own record (`dpkg-query -s`: richer than `apt-cache show
# PKG=<installed version>`, which gives the archive's record when that version is still listed: # PKG=<installed version>`, which gives the archive's record when that version is still listed:
# no Status, no Conffiles, often only the short description); a package dpkg merely remembers # no Status, no Conffiles, often only the short description); a package dpkg merely remembers
@@ -1292,9 +1292,11 @@ aur_warn_pending() {
} }
# aur_exec ARGS...: run the helper (as the invoking user when we are root), stdin from /dev/null, # aur_exec ARGS...: run the helper (as the invoking user when we are root), stdin from /dev/null,
# our lock descriptors closed, from that user's home # our lock descriptors closed, from that user's home; COLUMNS=0: yay wraps -Sia lists at 80 columns
# otherwise, even without a terminal (discovery/pacman.md 15.2; the merge unwraps anyway)
aur_exec() { aur_exec() {
( (
export COLUMNS=0
if [[ -n $AUR_HOME ]]; then cd -- "$AUR_HOME" 2>/dev/null || cd /; fi if [[ -n $AUR_HOME ]]; then cd -- "$AUR_HOME" 2>/dev/null || cd /; fi
run_child ${AUR_RUN[@]+"${AUR_RUN[@]}"} "$AUR_HELPER" "$@" run_child ${AUR_RUN[@]+"${AUR_RUN[@]}"} "$AUR_HELPER" "$@"
) )
@@ -1415,19 +1417,19 @@ pacman_describe() {
[[ -n $out ]] || return 1 [[ -n $out ]] || return 1
printf '%s\n' "$out" printf '%s\n' "$out"
} }
# pacman_full_info PKG: -Qi and -Si as pacman prints them (each record ends with a blank line). A # pacman_full_info PKG: -Qi and -Si as pacman prints them (each record ends with a blank line), with
# package in no repository: the AUR record when a helper is in use, every line marked with "> " # the " " gutter. A package in no repository: the AUR record when a helper is in use, every line
# (reached only when it does not merge with -Qi - paru's and yay's records do) # marked "* " (reached only when it does not merge with -Qi - paru's and yay's records do)
pacman_full_info() { pacman_full_info() {
local ok=1 qi si aur='' l local ok=1 qi si aur=''
qi=$(_pm -Qi -- "$1" 2>/dev/null) && ok=0 qi=$(_pm -Qi -- "$1" 2>/dev/null) && ok=0
si=$(_pm -Si -- "$1" 2>/dev/null) && ok=0 si=$(_pm -Si -- "$1" 2>/dev/null) && ok=0
if [[ -z $si ]] && aur_prepare; then aur=$(aur_info "$1") || aur=''; fi if [[ -z $si ]] && aur_prepare; then aur=$(aur_info "$1") || aur=''; fi
[[ -z $aur ]] || say "# lines starting with > are the AUR record ($AUR_HELPER -Sia) as it prints it" [[ -z $aur ]] || say "# lines starting with * are the AUR record ($AUR_HELPER -Sia) as it prints it"
[[ -z $qi ]] || printf '%s\n\n' "$qi" [[ -z $qi ]] || printf '%s\n\n' "$qi" | gutter
[[ -z $si ]] || printf '%s\n\n' "$si" [[ -z $si ]] || printf '%s\n\n' "$si" | gutter
if [[ -n $aur ]]; then if [[ -n $aur ]]; then
while IFS= read -r l; do printf '> %s\n' "$l"; done <<<"$aur" printf '%s\n' "$aur" | sed '/./s/^/* /'
ok=0 ok=0
fi fi
return "$ok" return "$ok"
@@ -1866,7 +1868,7 @@ dnf_describe() {
done <<<"$out" done <<<"$out"
return 1 return 1
} }
dnf_full_info() { "$DNF" -q info "$1" </dev/null; } dnf_full_info() { "$DNF" -q info "$1" </dev/null | gutter; }
# dnf_installed_record PKG / dnf_candidate_record PKG installed|not-installed: `dnf info` blocks # dnf_installed_record PKG / dnf_candidate_record PKG installed|not-installed: `dnf info` blocks
# without their heading line ("Installed Packages", dnf5 "Available upgrades", ...). The candidate # without their heading line ("Installed Packages", dnf5 "Available upgrades", ...). The candidate
# of an installed package is its upgrade (--upgrades: the installed architecture only, nothing for # of an installed package is its upgrade (--upgrades: the installed architecture only, nothing for
@@ -2053,7 +2055,7 @@ zypper_describe() {
out=$(rpm -q --qf '%{SUMMARY}\n' "$1" </dev/null 2>/dev/null) || return 1 out=$(rpm -q --qf '%{SUMMARY}\n' "$1" </dev/null 2>/dev/null) || return 1
printf '%s\n' "$out" printf '%s\n' "$out"
} }
zypper_full_info() { zypper --non-interactive --quiet info --requires "$1" </dev/null; } zypper_full_info() { zypper --non-interactive --quiet info --requires "$1" </dev/null | gutter; }
zypper_rdepends() { rpm_rdeps "$1"; } zypper_rdepends() { rpm_rdeps "$1"; }
zypper_closure() { rpm_closure "$1"; } zypper_closure() { rpm_closure "$1"; }
# zypper_reason PKG: the "Installed" line of `zypper info` - "Yes (automatically)", "Yes" or "No" # zypper_reason PKG: the "Installed" line of `zypper info` - "Yes (automatically)", "Yes" or "No"
@@ -2356,20 +2358,32 @@ print_info() {
# pacman, apt, dnf. zypper does not (rpm -qi and zypper info share almost no keys, and zypper cannot # pacman, apt, dnf. zypper does not (rpm -qi and zypper info share almost no keys, and zypper cannot
# print the installed record by itself) and keeps <be>_full_info, as does any package for which # print the installed record by itself) and keeps <be>_full_info, as does any package for which
# either side is missing or is not exactly one record. # either side is missing or is not exactly one record.
readonly SU_MERGE_NOTE='# lines starting with > come from the upstream candidate: a differing value, or a field the installed record lacks' readonly SU_MERGE_NOTE='# lines starting with * come from the upstream candidate: a differing value, or a field the installed record lacks'
readonly SU_CANDIDATE_NOTE='# not installed: this is the upstream candidate' readonly SU_CANDIDATE_NOTE='# not installed: lines starting with * come from the upstream candidate (all of them)'
# merge: each base field is printed as it is; when the candidate's value differs, the candidate's # gutter: the two-character gutter of N?? / show for native output shown as it is (" " before every
# line(s) follow, prefixed with "> "; fields only the candidate has are appended the same way. A # non-empty line; the merge itself uses " " for installed lines and "* " for the candidate's)
# field starts on a line beginning with a non-blank character that holds a ":" (key = the text gutter() { sed '/./s/^/ /'; }
# before it, trailing blanks dropped; a heading without ":" before the record is skipped); lines # merge: every line gets a two-character gutter: " " for the installed record, "* " for the
# beginning with a blank continue it (pacman Optional Deps, dnf Description, RFC-822 continuation # candidate. Each installed field is printed as it is; when the candidate's value differs, the
# lines), so multi-line values are compared - and prefixed - as blocks, every line trimmed. A blank # candidate's line(s) follow; fields only the candidate has are appended. A field starts on a line
# line ends a record: exit 3 unless each side holds exactly one. Description-xx (apt Translation # beginning with a non-blank character that holds a ":" (key = the text before it, trailing blanks
# files) pairs with Description. apt=1: the archives carry only the short description of the # dropped; a heading without ":" before the record is skipped); lines beginning with a blank continue
# candidate (the long text is in the Translation files), so a one-line candidate Description equal # it (pacman Optional Deps, dnf Description, RFC-822 continuation lines), so multi-line values are
# to the first installed line is the same description. When both records align every ":" in one # compared - and marked - as blocks, every line trimmed. A blank line ends a record: exit 3 unless
# column (pacman, dnf) but in different ones (dnf5 pads installed and available keys differently), # each side holds exactly one. Description-xx (apt Translation files) pairs with Description.
# the candidate lines are re-padded to the installed record's column. # apt=1: the archives carry only the short description of the candidate (the long text is in the
# Translation files), so a one-line candidate Description equal to the first installed line is the
# same description. When both records align every ":" in one column (pacman, dnf) but in different
# ones (dnf5 pads installed and available keys differently, yay -Sia pads to 30), the candidate lines
# are re-padded to the installed record's column.
# pm=1 (pacman: -Qi with -Si or an AUR helper's -Sia; discovery/pacman.md sections 14-15): values are
# compared unwrapped (continuation lines joined: the AUR helpers wrap long lists at the terminal
# width), runs of blanks as one; list fields (Depends On, Provides, ...) as token sets, in any order
# (a version constraint is part of its token); Optional Deps by package names only (-Qi prints
# "name: description [installed]" per line, the AUR prints bare names). A differing candidate value
# is one line, as -Qi prints it - but Optional Deps, one entry per line under the value column - and
# candidate-only fields go next to their installed counterpart (the anchor table below: Repository
# after Version, AUR URL after URL, ...), the others at the end in the candidate's order.
# shellcheck disable=SC2016 # awk program, not shell # shellcheck disable=SC2016 # awk program, not shell
readonly AWK_MERGE_RECORDS=' readonly AWK_MERGE_RECORDS='
function trim(s) { sub(/^[ \t]+/, "", s); sub(/[ \t\r]+$/, "", s); return s } function trim(s) { sub(/^[ \t]+/, "", s); sub(/[ \t\r]+$/, "", s); return s }
@@ -2379,7 +2393,52 @@ function repad(l, i, k) {
if (shift > 0) return sprintf("%" shift "s", "") l if (shift > 0) return sprintf("%" shift "s", "") l
return substr(l, 1 - shift) return substr(l, 1 - shift)
} }
function pfx(s, n, a, m) { n = split(s, a, "\n"); for (m = 1; m <= n; m++) print "> " repad(a[m]) } function mine(s, n, a, m) { n = split(s, a, "\n"); for (m = 1; m <= n; m++) print " " a[m] }
function theirs(s, n, a, m) { n = split(s, a, "\n"); for (m = 1; m <= n; m++) print "* " repad(a[m]) }
# kv K V: a "Key : value" line in the layout of the installed record
function kv(k, v) { return (col[1] > 0) ? sprintf("%-" (col[1] - 1) "s: %s", k, v) : k " : " v }
function isort(a, n, i, j, t) { for (i = 2; i <= n; i++) { t = a[i]; for (j = i - 1; j > 0 && a[j] > t; j--) a[j + 1] = a[j]; a[j + 1] = t } }
function joined(a, n, i, r) { r = ""; for (i = 1; i <= n; i++) r = r (i > 1 ? " " : "") a[i]; return r }
function flat(s) { gsub(/[ \t\n]+/, " ", s); return trim(s) }
# tokens S: the sorted tokens of a list value
function tokens(s, t, n, i, u, m) { n = split(flat(s), t, " "); m = 0; for (i = 1; i <= n; i++) if (t[i] != "") u[++m] = t[i]; isort(u, m); return joined(u, m) }
# entries S E: the entries of an Optional Deps value: one per line, or separated by 2+ blanks
function entries(s, e, n, a, m, c, b, x, ne) {
ne = 0; n = split(s, a, "\n")
for (m = 1; m <= n; m++) { c = split(a[m], b, /[ \t][ \t]+/); for (x = 1; x <= c; x++) { b[x] = trim(b[x]); if (b[x] != "") e[++ne] = b[x] } }
return ne
}
# names S: the sorted package names of an Optional Deps value ("None": none)
function names(s, e, n, i, x, u, m) {
n = entries(s, e); m = 0
for (i = 1; i <= n; i++) { x = e[i]; sub(/[ \t]*\[installed\]$/, "", x); sub(/:( .*)?$/, "", x); x = trim(x); if (x != "" && x != "None") u[++m] = x }
isort(u, m); return joined(u, m)
}
function same(k) {
if (!pm) return val[1, k] == val[2, k]
if (k == "Optional Deps") return names(val[1, k]) == names(val[2, k])
if (k in LIST) return tokens(val[1, k]) == tokens(val[2, k])
return flat(val[1, k]) == flat(val[2, k])
}
function up(k, e, n, i, ind, v) {
done[k] = 1
if (!pm) { theirs(raw[2, k]); return }
if (k == "Optional Deps") {
n = entries(val[2, k], e); ind = (col[1] > 0) ? col[1] + 1 : length(k) + 3
print "* " kv(k, e[1])
for (i = 2; i <= n; i++) print "* " sprintf("%" ind "s", "") e[i]
return
}
if (nl[2, k] == 1) { theirs(raw[2, k]); return }
v = val[2, k]; gsub(/\n/, (k in LIST) ? " " : " ", v)
print "* " kv(k, v)
}
BEGIN {
n = split("Groups|Provides|Depends On|Required By|Optional For|Conflicts With|Replaces|Make Deps|Check Deps|Licenses|Keywords", t, "|")
for (i = 1; i <= n; i++) LIST[t[i]] = 1
na = split("Repository>Version|AUR URL>URL|Keywords>Groups|Make Deps>Optional Deps|Check Deps>Optional Deps|Maintainer>Packager|Last Modified>Build Date|First Submitted>Build Date|Download Size>Installed Size", t, "|")
for (i = 1; i <= na; i++) { split(t[i], p, ">"); AK[i] = p[1]; AA[i] = p[2] }
}
FNR == 1 { side = (FILENAME == ARGV[1]) ? 1 : 2; cur = "" } FNR == 1 { side = (FILENAME == ARGV[1]) ? 1 : 2; cur = "" }
/^[ \t]*$/ { if (nk[side]) ended[side] = 1; next } /^[ \t]*$/ { if (nk[side]) ended[side] = 1; next }
/^[^ \t]/ { /^[^ \t]/ {
@@ -2405,23 +2464,23 @@ END {
shift = (col[1] > 0 && col[2] > 0) ? col[1] - col[2] : 0 shift = (col[1] > 0 && col[2] > 0) ? col[1] - col[2] : 0
for (j = 1; j <= nk[1]; j++) { for (j = 1; j <= nk[1]; j++) {
k = key[1, j] k = key[1, j]
print raw[1, k] mine(raw[1, k])
if (!((2, k) in raw) || val[1, k] == val[2, k]) continue if (((2, k) in raw) && !same(k) && !(apt && k == "Description" && nl[2, k] == 1 && val[2, k] == first[1, k])) up(k)
if (apt && k == "Description" && nl[2, k] == 1 && val[2, k] == first[1, k]) continue if (pm) for (a = 1; a <= na; a++) if (AA[a] == k && ((2, AK[a]) in raw) && !((1, AK[a]) in raw) && !(AK[a] in done)) up(AK[a])
pfx(raw[2, k])
} }
for (j = 1; j <= nk[2]; j++) { k = key[2, j]; if (!((1, k) in raw)) pfx(raw[2, k]) } for (j = 1; j <= nk[2]; j++) { k = key[2, j]; if (!((1, k) in raw) && !(k in done)) up(k) }
}' }'
print_full_info() { # print_full_info PKG [OLD] (N?? / show); 1 if the package manager knows nothing print_full_info() { # print_full_info PKG [OLD] (N?? / show); 1 if the package manager knows nothing
local rc=0 inst='' cand='' out apt=0 local rc=0 inst='' cand='' out apt=0 pm=0
if declare -F "${BACKEND}_installed_record" >/dev/null; then if declare -F "${BACKEND}_installed_record" >/dev/null; then
[[ $BACKEND != apt ]] || apt=1 [[ $BACKEND != apt ]] || apt=1
[[ $BACKEND != pacman ]] || pm=1
# a row whose old version is "-" is a new dependency: not installed, nothing to ask # a row whose old version is "-" is a new dependency: not installed, nothing to ask
[[ ${2:-} == - ]] || inst=$(be installed_record "$1") || inst='' [[ ${2:-} == - ]] || inst=$(be installed_record "$1") || inst=''
if [[ -n $inst ]]; then if [[ -n $inst ]]; then
cand=$(be candidate_record "$1" installed) || cand='' cand=$(be candidate_record "$1" installed) || cand=''
if [[ -n $cand ]] && out=$(awk -v apt="$apt" "$AWK_MERGE_RECORDS" <(printf '%s\n' "$inst") <(printf '%s\n' "$cand")); then if [[ -n $cand ]] && out=$(awk -v apt="$apt" -v pm="$pm" "$AWK_MERGE_RECORDS" <(printf '%s\n' "$inst") <(printf '%s\n' "$cand")); then
say "$SU_MERGE_NOTE" say "$SU_MERGE_NOTE"
say "$out" say "$out"
return 0 return 0
@@ -2430,11 +2489,12 @@ print_full_info() { # print_full_info PKG [OLD] (N?? / show); 1 if the package m
cand=$(be candidate_record "$1" not-installed) || cand='' cand=$(be candidate_record "$1" not-installed) || cand=''
if [[ -n $cand ]]; then if [[ -n $cand ]]; then
say "$SU_CANDIDATE_NOTE" say "$SU_CANDIDATE_NOTE"
say "$cand" printf '%s\n' "$cand" | sed '/./s/^/* /'
return 0 return 0
fi fi
fi fi
fi fi
# the native output as it is (<be>_full_info prints it with the gutter)
be full_info "$1" || rc=$? be full_info "$1" || rc=$?
((rc == 0)) && return 0 ((rc == 0)) && return 0
err "no package information for '$1' ($(be label) exited $rc)" err "no package information for '$1' ($(be label) exited $rc)"
+6 -6
View File
@@ -67,12 +67,12 @@ pacman_only() { [[ $BE == pacman ]] || skip "pacman backend only"; }
'su-dummy-aur: Skeptical-updater dummy package AUR (foreign: in no repository)' \ 'su-dummy-aur: Skeptical-updater dummy package AUR (foreign: in no repository)' \
'reason: explicitly installed' \ 'reason: explicitly installed' \
'required by: nothing')" 'required by: nothing')"
assert_line '# lines starting with > come from the upstream candidate: a differing value, or a field the installed record lacks' assert_line '# lines starting with * come from the upstream candidate: a differing value, or a field the installed record lacks'
assert_output --partial "$(printf '%s\n' 'Version : 1.0-1' '> Version : 2.0-1')" assert_output --partial "$(printf '%s\n' ' Version : 1.0-1' '* Version : 2.0-1' '* Repository : aur')"
assert_line 'Install Reason : Explicitly installed' assert_line ' Install Reason : Explicitly installed'
assert_line '> Repository : aur' assert_output --partial "$(printf '%s\n' ' URL : https://example.invalid/su-dummy' '* AUR URL : https://aur.archlinux.org/packages/su-dummy-aur')"
assert_line '> Maintainer : su-fixture' assert_output --partial "$(printf '%s\n' ' Packager : su-fixture <su-fixture@example.invalid>' '* Maintainer : su-fixture')"
assert_line '> AUR URL : https://aur.archlinux.org/packages/su-dummy-aur' assert_output --partial "$(printf '%s\n' ' Validated By : None' '* Votes : 7' '* Popularity : 0.01' '* Out Of Date : No')"
run grep -c 'su-tester HOME=/home/su-tester PWD=/home/su-tester -Sia su-dummy-aur' "$PARU_LOG" run grep -c 'su-tester HOME=/home/su-tester PWD=/home/su-tester -Sia su-dummy-aur' "$PARU_LOG"
assert_output 1 assert_output 1
} }
+14 -13
View File
@@ -60,29 +60,30 @@ zypper_verb() { case $(. /etc/os-release && printf '%s' "$ID") in opensuse-tumbl
run skeptical-updater --no-refresh <<<$'2??\nq' run skeptical-updater --no-refresh <<<$'2??\nq'
assert_success assert_success
if [[ $BE == zypper ]]; then # no comparable records: the plain zypper info --requires if [[ $BE == zypper ]]; then # no comparable records: the plain zypper info --requires
assert_line 'Information for package su-dummy-b:' assert_line ' Information for package su-dummy-b:'
refute_line --partial '# lines starting with >' refute_line --partial '# lines starting with *'
return 0 return 0
fi fi
assert_line '# lines starting with > come from the upstream candidate: a differing value, or a field the installed record lacks' assert_line '# lines starting with * come from the upstream candidate: a differing value, or a field the installed record lacks'
case $BE in case $BE in
apt) apt)
assert_line 'Package: su-dummy-b' assert_line ' Package: su-dummy-b'
assert_line 'Status: install ok installed' assert_line ' Status: install ok installed'
assert_output --partial "$(printf '%s\n' 'Version: 1.0' '> Version: 2.0')" assert_output --partial "$(printf '%s\n' ' Version: 1.0' '* Version: 2.0')"
assert_line --regexp '^> Filename: .*su-dummy-b_2\.0_all\.deb$' assert_line --regexp '^\* Filename: .*su-dummy-b_2\.0_all\.deb$'
;; ;;
pacman) pacman)
assert_output --partial "$(printf '%s\n' 'Version : 1.0-1' '> Version : 2.0-1')" assert_output --partial "$(printf '%s\n' ' Version : 1.0-1' '* Version : 2.0-1' '* Repository : su-dummy')"
assert_line 'Install Reason : Installed as a dependency for another package' assert_line ' Install Reason : Installed as a dependency for another package'
assert_line '> Repository : su-dummy'
;; ;;
dnf) dnf)
assert_output --partial "$(printf '%s\n' 'Version : 1.0' '> Version : 2.0')" assert_output --partial "$(printf '%s\n' ' Version : 1.0' '* Version : 2.0')"
assert_output --partial "$(printf '%s\n' 'Repository : @System' '> Repository : su-dummy')" assert_output --partial "$(printf '%s\n' ' Repository : @System' '* Repository : su-dummy')"
;; ;;
esac esac
[[ $(grep -c '^> ' <<<"$output") -ge 2 ]] [[ $(grep -c '^\* ' <<<"$output") -ge 2 ]]
# every line has the two-character gutter, except the note
[[ $(grep -cvE '^(# lines starting with| |\* |$)' <<<"$(sed -n '/^# lines starting with/,/^> q$/p' <<<"$output" | sed '$d')") -eq 0 ]]
} }
@test "N+ writes the user row and the dep row; list hides both and counts them" { @test "N+ writes the user row and the dep row; list hides both and counts them" {
+147 -35
View File
@@ -314,22 +314,30 @@ upgrade_steps() {
run skup --no-refresh <<<$'1??\nq' run skup --no-refresh <<<$'1??\nq'
assert_success assert_success
log_has "$AS_ALICE paru -Sia pfetch" log_has "$AS_ALICE paru -Sia pfetch"
assert_line '# lines starting with > come from the upstream candidate: a differing value, or a field the installed record lacks' assert_line '# lines starting with * come from the upstream candidate: a differing value, or a field the installed record lacks'
assert_output --partial "$(printf '%s\n' 'Version : 1.10.0-1' '> Version : 1.11.0-1')" assert_output --partial "$(printf '%s\n' ' Version : 1.10.0-1' '* Version : 1.11.0-1' '* Repository : aur')"
assert_line 'Install Reason : Explicitly installed' assert_line ' Install Reason : Explicitly installed'
assert_line 'Description : A pretty system information tool written in POSIX sh.' assert_line ' Description : A pretty system information tool written in POSIX sh.'
[[ $(count_lines 'Description : A pretty system information tool written in POSIX sh.') -eq 1 ]] [[ $(grep -c 'Description : A pretty system' <<<"$output") -eq 1 ]]
# the AUR-only fields next to their installed counterparts, the rest at the end
assert_output --partial "$(printf '%s\n' \ assert_output --partial "$(printf '%s\n' \
'> Repository : aur' \ ' URL : https://github.com/Un1q32/pfetch' \
'> AUR URL : https://aur.archlinux.org/packages/pfetch' \ '* AUR URL : https://aur.archlinux.org/packages/pfetch')"
'> Make Deps : None' \ assert_output --partial "$(printf '%s\n' \
'> Check Deps : None' \ ' Optional Deps : None' \
'> Maintainer : yigits' \ '* Make Deps : None' \
'> Votes : 48' \ '* Check Deps : None')"
'> Popularity : 0.10344' \ assert_output --partial "$(printf '%s\n' \
'> First Submitted : Sat, 16 Jan 2021 09:13:08' \ ' Packager : Unknown Packager' \
'> Last Modified : Tue, 9 Jun 2026 22:43:50' \ '* Maintainer : yigits' \
'> Out Of Date : No')" ' Build Date : Sat Oct 10 12:42:02 2026' \
'* Last Modified : Tue, 9 Jun 2026 22:43:50' \
'* First Submitted : Sat, 16 Jan 2021 09:13:08')"
assert_output --partial "$(printf '%s\n' \
' Validated By : None' \
'* Votes : 48' \
'* Popularity : 0.10344' \
'* Out Of Date : No')"
} }
@test "N?? with yay: its wider key column is re-padded to pacman's" { @test "N?? with yay: its wider key column is re-padded to pacman's" {
@@ -337,45 +345,149 @@ upgrade_steps() {
run skup --no-refresh <<<$'1??\nq' run skup --no-refresh <<<$'1??\nq'
assert_success assert_success
log_has "$AS_ALICE yay -Sia pfetch" log_has "$AS_ALICE yay -Sia pfetch"
assert_output --partial "$(printf '%s\n' 'Version : 1.10.0-1' '> Version : 1.11.0-1')" assert_output --partial "$(printf '%s\n' ' Version : 1.10.0-1' '* Version : 1.11.0-1' '* Repository : aur')"
assert_line '> Repository : aur' assert_output --partial "$(printf '%s\n' ' Groups : None' '* Keywords : fetch')"
assert_line '> Keywords : fetch' assert_line '* First Submitted : Sat 16 Jan 2021 09:13:08 AM UTC'
assert_line '> First Submitted : Sat 16 Jan 2021 09:13:08 AM UTC' assert_output --partial "$(printf '%s\n' ' Validated By : None' '* Popularity : 0.103440' '* Votes : 48' '* Out-of-date : No')"
assert_line '> Out-of-date : No'
refute_output --regexp 'Repository {10,}:' refute_output --regexp 'Repository {10,}:'
} }
@test "N?? when the AUR has no record of it: the installed record alone" { @test "N?? when the AUR has no record of it: the installed record alone" {
run skup --no-refresh <<<$'3??\nq' run skup --no-refresh <<<$'3??\nq'
assert_success assert_success
assert_line 'Name : su-dummy-aur' assert_line ' Name : su-dummy-aur'
assert_line 'Install Reason : Explicitly installed' assert_line ' Install Reason : Explicitly installed'
refute_line --regexp '^> [A-Z#]' refute_line --regexp '^\* '
log_has "$AS_ALICE paru -Sia su-dummy-aur" log_has "$AS_ALICE paru -Sia su-dummy-aur"
} }
@test "N?? when the AUR record does not merge: -Qi, then the AUR record with every line marked >" { @test "N?? claude-desktop (recorded): yay's wrapped record unwrapped, one-line lists, Optional Deps per entry, AUR fields in place" {
# real records: the installed 2.9939.4-1 (pacman -Qi of a stand-in built from the AUR's own
# .SRCINFO of that version) and yay 13.0.1's -Sia of 2.31226.1-1, wrapped at 80 columns as yay
# prints it without COLUMNS=0 (discovery/pacman.md section 15.2)
use_fakes yay pacman runuser getent
run skup show claude-desktop
assert_success
assert_output "$(cat <<'EOF'
# lines starting with * come from the upstream candidate: a differing value, or a field the installed record lacks
Name : claude-desktop
Version : 2.9939.4-1
* Version : 2.31226.1-1
* Repository : aur
Description : Official Claude AI desktop app from Anthropic — Chat, Cowork, and Claude Code
Architecture : x86_64
URL : https://claude.com/download
* AUR URL : https://aur.archlinux.org/packages/claude-desktop
Licenses : LicenseRef-Proprietary
Groups : None
* Keywords : None
Provides : None
Depends On : alsa-lib at-spi2-core cairo dbus expat gcc-libs glib2 glibc gtk3 hicolor-icon-theme libcap-ng libcups libdrm libnotify libseccomp libsecret libx11 libxcb libxcomposite libxdamage libxext libxfixes libxkbcommon libxrandr libxtst mesa nspr nss pango socat systemd-libs util-linux-libs virtiofsd xdg-desktop-portal xdg-utils
* Depends On : alsa-lib at-spi2-core bubblewrap cairo dbus expat gcc-libs glib2 glibc gtk3 hicolor-icon-theme libcap-ng libcups libdrm libnotify libpipewire libseccomp libsecret libx11 libxcb libxcomposite libxdamage libxext libxfixes libxkbcommon libxrandr libxtst mesa nspr nss pango socat systemd-libs util-linux-libs virtiofsd xdg-desktop-portal xdg-utils qemu-system-x86 edk2-ovmf
Optional Deps : gnome-keyring: credential storage via Secret Service (GNOME)
kwallet: credential storage (KDE Plasma) [installed]
libayatana-appindicator: system tray icon
xdg-desktop-portal-gtk: portal backend for GTK desktops
xdg-desktop-portal-kde: portal backend for KDE Plasma
* Optional Deps : gnome-keyring
* kwallet
* libayatana-appindicator
* xdg-desktop-portal-gtk
* xdg-desktop-portal-kde
* qemu-system-aarch64
* edk2-aarch64
* Make Deps : None
* Check Deps : None
Required By : None
Optional For : None
Conflicts With : claude claude-desktop-bin
Replaces : None
Installed Size : 117.74 MiB
Packager : Unknown Packager
* Maintainer : diazkev314
Build Date : Sat Oct 3 04:00:00 2026
* Last Modified : Fri 09 Oct 2026 11:31:19 PM UTC
* First Submitted : Sun 05 Jul 2026 01:58:02 AM UTC
Install Date : Sat Oct 10 13:49:52 2026
Install Reason : Explicitly installed
Install Script : No
Validated By : None
* Popularity : 6.458490
* Votes : 21
* Out-of-date : No
EOF
)"
log_has "$AS_ALICE yay -Sia claude-desktop"
}
@test "N?? claude-desktop: paru's unwrapped record merges to the same Depends On and Optional Deps lines" {
use_fakes yay pacman runuser getent
run skup show claude-desktop
yay_out=$output
use_fakes paru pacman runuser getent
run skup show claude-desktop
assert_success
[[ $(grep -A8 '^ Depends On' <<<"$output") == "$(grep -A8 '^ Depends On' <<<"$yay_out")" ]] ||
fail "paru and yay merge Depends On / Optional Deps differently"
assert_line '* Out Of Date : No'
}
@test "N?? pacman lists: a wrapped, reordered Depends On and bare Optional Deps names equal to the installed sets print once" {
use_fakes yay pacman runuser getent
FAKE_AUR_SIA=same run skup show claude-desktop
assert_success
[[ $(count_lines ' Depends On : alsa-lib at-spi2-core cairo dbus expat gcc-libs glib2 glibc gtk3 hicolor-icon-theme libcap-ng libcups libdrm libnotify libseccomp libsecret libx11 libxcb libxcomposite libxdamage libxext libxfixes libxkbcommon libxrandr libxtst mesa nspr nss pango socat systemd-libs util-linux-libs virtiofsd xdg-desktop-portal xdg-utils') -eq 1 ]]
refute_line --regexp '^\* +(Depends On|Optional Deps|Conflicts With) *:'
refute_line --regexp '^\* {18,}'
assert_output --partial "$(printf '%s\n' \
' kwallet: credential storage (KDE Plasma) [installed]' \
' libayatana-appindicator: system tray icon' \
' xdg-desktop-portal-gtk: portal backend for GTK desktops' \
' xdg-desktop-portal-kde: portal backend for KDE Plasma' \
'* Make Deps : None')"
}
@test "N?? pacman lists: an added token or a changed version constraint is a difference (one * line, as -Qi prints it)" {
use_fakes yay pacman runuser getent
FAKE_AUR_SIA=constraint run skup show claude-desktop
assert_success
assert_line --regexp '^\* Depends On : xdg-utils xdg-desktop-portal virtiofsd .* pango nss>=3\.100 nspr .* at-spi2-core alsa-lib$'
[[ $(grep -c '^\* Depends On' <<<"$output") -eq 1 ]]
refute_line --regexp '^\* +(Optional Deps|Conflicts With) *:'
run skup show claude-desktop
assert_line --regexp '^\* Depends On : alsa-lib at-spi2-core bubblewrap cairo .* libnotify libpipewire libseccomp .* xdg-utils qemu-system-x86 edk2-ovmf$'
# an extra upstream name in Optional Deps: the upstream block, one entry per line
assert_output --partial "$(printf '%s\n' \
'* Optional Deps : gnome-keyring' \
'* kwallet' \
'* libayatana-appindicator' \
'* xdg-desktop-portal-gtk' \
'* xdg-desktop-portal-kde' \
'* qemu-system-aarch64' \
'* edk2-aarch64')"
}
@test "N?? when the AUR record does not merge: -Qi, then the AUR record with every line marked *" {
FAKE_AUR_SIA=twice run skup --no-refresh <<<$'1??\nq' FAKE_AUR_SIA=twice run skup --no-refresh <<<$'1??\nq'
assert_success assert_success
assert_line '# lines starting with > are the AUR record (paru -Sia) as it prints it' assert_line '# lines starting with * are the AUR record (paru -Sia) as it prints it'
refute_line --partial '# lines starting with > come from the upstream candidate' refute_line --partial '# lines starting with * come from the upstream candidate'
assert_line 'Version : 1.10.0-1' assert_line ' Version : 1.10.0-1'
[[ $(count_lines '> Version : 1.11.0-1') -eq 2 ]] [[ $(count_lines '* Version : 1.11.0-1') -eq 2 ]]
[[ $(count_lines '> Repository : aur') -eq 2 ]] [[ $(count_lines '* Repository : aur') -eq 2 ]]
assert_line 'Install Reason : Explicitly installed' assert_line ' Install Reason : Explicitly installed'
} }
@test "show PKG for an AUR package: the same merge, the helper decided on the spot" { @test "show PKG for an AUR package: the same merge, the helper decided on the spot" {
run skup show pfetch run skup show pfetch
assert_success assert_success
assert_line '> Repository : aur' assert_output --partial "$(printf '%s\n' ' Version : 1.10.0-1' '* Version : 1.11.0-1' '* Repository : aur')"
assert_output --partial "$(printf '%s\n' 'Version : 1.10.0-1' '> Version : 1.11.0-1')"
export SKEPTICAL_UPDATER_EUID=1000 export SKEPTICAL_UPDATER_EUID=1000
unset SUDO_USER unset SUDO_USER
: >"$FAKE_LOG" : >"$FAKE_LOG"
run skup show pfetch run skup show pfetch
assert_success assert_success
assert_line '> Maintainer : yigits' assert_line '* Maintainer : yigits'
log_has 'paru -Sia pfetch' log_has 'paru -Sia pfetch'
log_lacks '^runuser' log_lacks '^runuser'
} }
@@ -384,8 +496,8 @@ upgrade_steps() {
unset SUDO_USER unset SUDO_USER
run skup show pfetch run skup show pfetch
assert_success assert_success
assert_line 'Name : pfetch' assert_line ' Name : pfetch'
refute_line --regexp '^> ' refute_line --regexp '^\* '
[[ $(grep -c 'warning: aur:' <<<"$output") -eq 1 ]] [[ $(grep -c 'warning: aur:' <<<"$output") -eq 1 ]]
log_lacks '^paru' log_lacks '^paru'
} }
@@ -0,0 +1,21 @@
Repository : aur
Name : claude-desktop
Version : 2.31226.1-1
Description : Official Claude AI desktop app from Anthropic — Chat, Cowork, and Claude Code
URL : https://claude.com/download
AUR URL : https://aur.archlinux.org/packages/claude-desktop
Groups : None
Licenses : LicenseRef-Proprietary
Provides : None
Depends On : alsa-lib at-spi2-core bubblewrap cairo dbus expat gcc-libs glib2 glibc gtk3 hicolor-icon-theme libcap-ng libcups libdrm libnotify libpipewire libseccomp libsecret libx11 libxcb libxcomposite libxdamage libxext libxfixes libxkbcommon libxrandr libxtst mesa nspr nss pango socat systemd-libs util-linux-libs virtiofsd xdg-desktop-portal xdg-utils qemu-system-x86 edk2-ovmf
Make Deps : None
Check Deps : None
Optional Deps : gnome-keyring kwallet libayatana-appindicator xdg-desktop-portal-gtk xdg-desktop-portal-kde qemu-system-aarch64 edk2-aarch64
Conflicts With : claude claude-desktop-bin
Maintainer : diazkev314
Votes : 21
Popularity : 6.45849
First Submitted : Sun, 5 Jul 2026 01:58:02
Last Modified : Fri, 9 Oct 2026 23:31:19
Out Of Date : No
@@ -0,0 +1,31 @@
Repository : aur
Name : claude-desktop
Version : 2.31226.1-1
Description : Official Claude AI desktop app from Anthropic — Chat, Cowork, and Claude Code
URL : https://claude.com/download
Licenses : LicenseRef-Proprietary
Groups : None
Provides : None
Depends On : xdg-utils xdg-desktop-portal virtiofsd
util-linux-libs systemd-libs socat pango nss>=3.100
nspr mesa libxtst libxrandr libxkbcommon
libxfixes libxext libxdamage libxcomposite
libxcb libx11 libsecret libseccomp libnotify
libdrm libcups libcap-ng hicolor-icon-theme gtk3
glibc glib2 gcc-libs expat dbus cairo
at-spi2-core alsa-lib
Optional Deps : xdg-desktop-portal-kde xdg-desktop-portal-gtk
libayatana-appindicator kwallet gnome-keyring
Make Deps : None
Check Deps : None
Conflicts With : claude claude-desktop-bin
Replaces : None
AUR URL : https://aur.archlinux.org/packages/claude-desktop
First Submitted : Sun 05 Jul 2026 01:58:02 AM UTC
Keywords : None
Last Modified : Fri 09 Oct 2026 11:31:19 PM UTC
Maintainer : diazkev314
Popularity : 6.458490
Votes : 21
Out-of-date : No
@@ -0,0 +1,31 @@
Repository : aur
Name : claude-desktop
Version : 2.31226.1-1
Description : Official Claude AI desktop app from Anthropic — Chat, Cowork, and Claude Code
URL : https://claude.com/download
Licenses : LicenseRef-Proprietary
Groups : None
Provides : None
Depends On : xdg-utils xdg-desktop-portal virtiofsd
util-linux-libs systemd-libs socat pango nss
nspr mesa libxtst libxrandr libxkbcommon
libxfixes libxext libxdamage libxcomposite
libxcb libx11 libsecret libseccomp libnotify
libdrm libcups libcap-ng hicolor-icon-theme gtk3
glibc glib2 gcc-libs expat dbus cairo
at-spi2-core alsa-lib
Optional Deps : xdg-desktop-portal-kde xdg-desktop-portal-gtk
libayatana-appindicator kwallet gnome-keyring
Make Deps : None
Check Deps : None
Conflicts With : claude claude-desktop-bin
Replaces : None
AUR URL : https://aur.archlinux.org/packages/claude-desktop
First Submitted : Sun 05 Jul 2026 01:58:02 AM UTC
Keywords : None
Last Modified : Fri 09 Oct 2026 11:31:19 PM UTC
Maintainer : diazkev314
Popularity : 6.458490
Votes : 21
Out-of-date : No
@@ -0,0 +1,34 @@
Repository : aur
Name : claude-desktop
Version : 2.31226.1-1
Description : Official Claude AI desktop app from Anthropic — Chat, Cowork, and Claude Code
URL : https://claude.com/download
Licenses : LicenseRef-Proprietary
Groups : None
Provides : None
Depends On : alsa-lib at-spi2-core bubblewrap cairo dbus
expat gcc-libs glib2 glibc gtk3
hicolor-icon-theme libcap-ng libcups libdrm
libnotify libpipewire libseccomp libsecret
libx11 libxcb libxcomposite libxdamage
libxext libxfixes libxkbcommon libxrandr
libxtst mesa nspr nss pango socat
systemd-libs util-linux-libs virtiofsd
xdg-desktop-portal xdg-utils qemu-system-x86
edk2-ovmf
Optional Deps : gnome-keyring kwallet libayatana-appindicator
xdg-desktop-portal-gtk xdg-desktop-portal-kde
qemu-system-aarch64 edk2-aarch64
Make Deps : None
Check Deps : None
Conflicts With : claude claude-desktop-bin
Replaces : None
AUR URL : https://aur.archlinux.org/packages/claude-desktop
First Submitted : Sun 05 Jul 2026 01:58:02 AM UTC
Keywords : None
Last Modified : Fri 09 Oct 2026 11:31:19 PM UTC
Maintainer : diazkev314
Popularity : 6.458490
Votes : 21
Out-of-date : No
+26
View File
@@ -332,3 +332,29 @@ Install Date : Fri Oct 9 08:00:00 2026
Install Reason : Explicitly installed Install Reason : Explicitly installed
Install Script : No Install Script : No
Validated By : None Validated By : None
Name : claude-desktop
Version : 2.9939.4-1
Description : Official Claude AI desktop app from Anthropic — Chat, Cowork, and Claude Code
Architecture : x86_64
URL : https://claude.com/download
Licenses : LicenseRef-Proprietary
Groups : None
Provides : None
Depends On : alsa-lib at-spi2-core cairo dbus expat gcc-libs glib2 glibc gtk3 hicolor-icon-theme libcap-ng libcups libdrm libnotify libseccomp libsecret libx11 libxcb libxcomposite libxdamage libxext libxfixes libxkbcommon libxrandr libxtst mesa nspr nss pango socat systemd-libs util-linux-libs virtiofsd xdg-desktop-portal xdg-utils
Optional Deps : gnome-keyring: credential storage via Secret Service (GNOME)
kwallet: credential storage (KDE Plasma) [installed]
libayatana-appindicator: system tray icon
xdg-desktop-portal-gtk: portal backend for GTK desktops
xdg-desktop-portal-kde: portal backend for KDE Plasma
Required By : None
Optional For : None
Conflicts With : claude claude-desktop-bin
Replaces : None
Installed Size : 117.74 MiB
Packager : Unknown Packager
Build Date : Sat Oct 3 04:00:00 2026
Install Date : Sat Oct 10 13:49:52 2026
Install Reason : Explicitly installed
Install Script : No
Validated By : None
+7 -2
View File
@@ -11,8 +11,9 @@
# noise (yay's colored root warning on stdout), none (nothing to upgrade: exit 1, no output) or # noise (yay's colored root warning on stdout), none (nothing to upgrade: exit 1, no output) or
# fail (the AUR is unreachable: exit 1 with the recorded error). Packages purged by the fake pacman # fail (the AUR is unreachable: exit 1 with the recorded error). Packages purged by the fake pacman
# are not listed. -Sia PKG prints data/aur/Sia-<helper>-PKG.txt (else the helper's not-found # are not listed. -Sia PKG prints data/aur/Sia-<helper>-PKG.txt (else the helper's not-found
# error, exit 1), twice with FAKE_AUR_SIA=twice (a record that cannot be merged); -Sua exits # error, exit 1), twice with FAKE_AUR_SIA=twice (a record that cannot be merged), or the variant
# FAKE_AUR_SUA_RC (default 0). # Sia-<helper>-PKG-<FAKE_AUR_SIA>.txt when it exists; -Sua exits FAKE_AUR_SUA_RC (default 0).
# claude-desktop: yay's record is the wrapped one yay prints without COLUMNS=0, recorded verbatim.
# shellcheck source=../fakelib.bash # shellcheck source=../fakelib.bash
. "${BASH_SOURCE[0]%/*}/../fakelib.bash" . "${BASH_SOURCE[0]%/*}/../fakelib.bash"
fake_log "$@" fake_log "$@"
@@ -38,6 +39,10 @@ case ${1:-} in
printf '%s\n' "$out" printf '%s\n' "$out"
;; ;;
-Sia) -Sia)
if [[ -n ${FAKE_AUR_SIA:-} && -f $D/Sia-$FAKE_NAME-${2:-}-$FAKE_AUR_SIA.txt ]]; then
cat "$D/Sia-$FAKE_NAME-$2-$FAKE_AUR_SIA.txt"
exit 0
fi
if [[ -f $D/Sia-$FAKE_NAME-${2:-}.txt ]]; then if [[ -f $D/Sia-$FAKE_NAME-${2:-}.txt ]]; then
cat "$D/Sia-$FAKE_NAME-$2.txt" cat "$D/Sia-$FAKE_NAME-$2.txt"
[[ ${FAKE_AUR_SIA:-} != twice ]] || cat "$D/Sia-$FAKE_NAME-$2.txt" [[ ${FAKE_AUR_SIA:-} != twice ]] || cat "$D/Sia-$FAKE_NAME-$2.txt"
+4 -5
View File
@@ -93,11 +93,10 @@ UPGRADE='apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--forc
@test "N?? prints the installed record merged with the candidate" { @test "N?? prints the installed record merged with the candidate" {
run skup --no-refresh <<<$'1??\nq' run skup --no-refresh <<<$'1??\nq'
assert_success assert_success
assert_line '# lines starting with > come from the upstream candidate: a differing value, or a field the installed record lacks' assert_line '# lines starting with * come from the upstream candidate: a differing value, or a field the installed record lacks'
assert_line 'Package: su-dummy-a' assert_line ' Package: su-dummy-a'
assert_line 'Version: 1.0' assert_output --partial "$(printf '%s\n' ' Version: 1.0' '* Version: 2.0')"
assert_line '> Version: 2.0' assert_line '* Depends: su-dummy-b (>= 2.0)'
assert_line '> Depends: su-dummy-b (>= 2.0)'
log_has 'dpkg-query -s -- su-dummy-a' log_has 'dpkg-query -s -- su-dummy-a'
log_has 'apt-cache show --no-all-versions su-dummy-a' log_has 'apt-cache show --no-all-versions su-dummy-a'
} }
+135 -135
View File
@@ -117,36 +117,35 @@ setup() { common_setup; }
run skup show su-dummy-b run skup show su-dummy-b
assert_success assert_success
assert_output "$(cat <<'EOF' assert_output "$(cat <<'EOF'
# lines starting with > come from the upstream candidate: a differing value, or a field the installed record lacks # lines starting with * come from the upstream candidate: a differing value, or a field the installed record lacks
Package: su-dummy-b Package: su-dummy-b
Status: install ok installed Status: install ok installed
Priority: optional Priority: optional
Section: misc Section: misc
Maintainer: Skeptical Updater Fixture <su-fixture@example.invalid> Maintainer: Skeptical Updater Fixture <su-fixture@example.invalid>
Architecture: all Architecture: all
Version: 1.0 Version: 1.0
> Version: 2.0 * Version: 2.0
Conffiles: Conffiles:
/etc/su-dummy/su-dummy-b.conf 607e2d3a119dfe832d937ce579ef1079 /etc/su-dummy/su-dummy-b.conf 607e2d3a119dfe832d937ce579ef1079
Description: dummy package su-dummy-b for skeptical-updater tests Description: dummy package su-dummy-b for skeptical-updater tests
This package only exists to exercise the package manager. This package only exists to exercise the package manager.
. .
It ships one data file and one conffile (version 1.0). It ships one data file and one conffile (version 1.0).
> Description: dummy package su-dummy-b for skeptical-updater tests * Description: dummy package su-dummy-b for skeptical-updater tests
> This package only exists to exercise the package manager. * This package only exists to exercise the package manager.
> . * .
> It ships one data file and one conffile (version 2.0). * It ships one data file and one conffile (version 2.0).
> Description-md5: 36da7dcdaf4ec207be2937d1f5dfa408 * Description-md5: 36da7dcdaf4ec207be2937d1f5dfa408
> Filename: ./su-dummy-b_2.0_all.deb * Filename: ./su-dummy-b_2.0_all.deb
> Size: 840 * Size: 840
EOF EOF
)" )"
log_has 'dpkg-query -s -- su-dummy-b' log_has 'dpkg-query -s -- su-dummy-b'
log_has 'apt-cache show --no-all-versions su-dummy-b' log_has 'apt-cache show --no-all-versions su-dummy-b'
run skup show su-dummy-a run skup show su-dummy-a
assert_success assert_success
assert_line 'Depends: su-dummy-b' assert_output --partial "$(printf '%s\n' ' Depends: su-dummy-b' '* Depends: su-dummy-b (>= 2.0)')"
assert_line '> Depends: su-dummy-b (>= 2.0)'
run skup show no-such-pkg-xyz run skup show no-such-pkg-xyz
assert_failure 1 assert_failure 1
assert_output --partial "no package information for 'no-such-pkg-xyz' (apt exited 100)" assert_output --partial "no package information for 'no-such-pkg-xyz' (apt exited 100)"
@@ -156,22 +155,22 @@ EOF
# real debian:12 records: perl-base 5.36.0-7+deb12u3 installed, +deb12u4 from bookworm-security # real debian:12 records: perl-base 5.36.0-7+deb12u3 installed, +deb12u4 from bookworm-security
run skup show perl-base run skup show perl-base
assert_success assert_success
[[ $(grep -c '^Description: minimal Perl system$' <<<"$output") -eq 1 ]] [[ $(grep -c '^ Description: minimal Perl system$' <<<"$output") -eq 1 ]]
refute_line --partial '> Description:' refute_line --partial '* Description:'
assert_output --partial "$(printf '%s\n' 'Installed-Size: 7639' '> Installed-Size: 7643')" assert_output --partial "$(printf '%s\n' ' Installed-Size: 7639' '* Installed-Size: 7643')"
assert_output --partial "$(printf '%s\n' 'Version: 5.36.0-7+deb12u3' '> Version: 5.36.0-7+deb12u4')" assert_output --partial "$(printf '%s\n' ' Version: 5.36.0-7+deb12u3' '* Version: 5.36.0-7+deb12u4')"
[[ $(grep -c '^Breaks: ' <<<"$output") -eq 1 ]] [[ $(grep -c '^ Breaks: ' <<<"$output") -eq 1 ]]
refute_line --regexp '^> (Breaks|Pre-Depends|Essential|Source):' refute_line --regexp '^\* (Breaks|Pre-Depends|Essential|Source):'
assert_line --index -1 '> SHA256: d7d1943aec9597629bf73075efcc5ef6dc9bda96d78e80d843156ecd448478b8' assert_line --index -1 '* SHA256: d7d1943aec9597629bf73075efcc5ef6dc9bda96d78e80d843156ecd448478b8'
} }
@test "apt: show of a package that is not installed prints the candidate alone, with a note" { @test "apt: show of a package that is not installed prints the candidate alone, with a note" {
run skup show zsh run skup show zsh
assert_success assert_success
assert_line --index 0 '# not installed: this is the upstream candidate' assert_line --index 0 '# not installed: lines starting with * come from the upstream candidate (all of them)'
assert_line --index 1 'Package: zsh' assert_line --index 1 '* Package: zsh'
assert_line --index 3 'Version: 5.9-4+b15' assert_line --index 3 '* Version: 5.9-4+b15'
refute_line --partial '> ' refute_line --regexp '^ '
log_has 'dpkg-query -s -- zsh' log_has 'dpkg-query -s -- zsh'
} }
@@ -272,48 +271,49 @@ EOF
assert_output 'trusted su-dummy-a (+1 dependency: su-dummy-b)' assert_output 'trusted su-dummy-a (+1 dependency: su-dummy-b)'
} }
@test "pacman: show merges -Qi with -Si of the candidate's repository: '>' lines for what differs" { @test "pacman: show merges -Qi with -Si of the candidate's repository: '*' lines for what differs, in place" {
# real archlinux records (glib2 2.88.3-1 installed, core 2.90.1-1), one Optional Deps entry # real archlinux records (glib2 2.88.3-1 installed, core 2.90.1-1), one Optional Deps entry
# added to the candidate: changed fields, identical ones once, -Qi-only fields kept, a differing # added to the candidate: changed fields, identical ones once, -Qi-only fields kept, a differing
# multi-line block, -Si-only fields appended # Optional Deps block (one entry per line), -Si-only fields next to their counterparts
# (Repository after Version, Download Size after Installed Size); every line in the gutter
use_fakes pacman use_fakes pacman
run skup show glib2 run skup show glib2
assert_success assert_success
assert_output "$(cat <<'EOF' assert_output "$(cat <<'EOF'
# lines starting with > come from the upstream candidate: a differing value, or a field the installed record lacks # lines starting with * come from the upstream candidate: a differing value, or a field the installed record lacks
Name : glib2 Name : glib2
Version : 2.88.3-1 Version : 2.88.3-1
> Version : 2.90.1-1 * Version : 2.90.1-1
Description : Low level core library * Repository : core
Architecture : x86_64 Description : Low level core library
URL : https://gitlab.gnome.org/GNOME/glib Architecture : x86_64
Licenses : LGPL-2.1-or-later URL : https://gitlab.gnome.org/GNOME/glib
Groups : None Licenses : LGPL-2.1-or-later
Provides : libglib-2.0.so=0-64 libgio-2.0.so=0-64 libgirepository-2.0.so=0-64 libgmodule-2.0.so=0-64 libgobject-2.0.so=0-64 libgthread-2.0.so=0-64 Groups : None
Depends On : bash glibc libffi libsysprof-capture pcre2 util-linux-libs zlib libffi.so=8-64 libmount.so=1-64 Provides : libglib-2.0.so=0-64 libgio-2.0.so=0-64 libgirepository-2.0.so=0-64 libgmodule-2.0.so=0-64 libgobject-2.0.so=0-64 libgthread-2.0.so=0-64
Optional Deps : dconf: GSettings storage backend Depends On : bash glibc libffi libsysprof-capture pcre2 util-linux-libs zlib libffi.so=8-64 libmount.so=1-64
glib2-devel: development tools Optional Deps : dconf: GSettings storage backend
gvfs: most gio functionality glib2-devel: development tools
> Optional Deps : dconf: GSettings storage backend gvfs: most gio functionality
> glib2-devel: development tools * Optional Deps : dconf: GSettings storage backend
> gvfs: most gio functionality * glib2-devel: development tools
> python: gdbus-codegen * gvfs: most gio functionality
Required By : libsecret pinentry * python: gdbus-codegen
Optional For : None Required By : libsecret pinentry
Conflicts With : None Optional For : None
Replaces : None Conflicts With : None
Installed Size : 37.89 MiB Replaces : None
> Installed Size : 38.39 MiB Installed Size : 37.89 MiB
Packager : Jan Alexander Steffens (heftig) <heftig@archlinux.org> * Installed Size : 38.39 MiB
Build Date : Wed Jul 29 14:57:16 2026 * Download Size : 5.12 MiB
> Build Date : Tue Oct 6 16:19:12 2026 Packager : Jan Alexander Steffens (heftig) <heftig@archlinux.org>
Install Date : Sun Oct 4 00:00:00 2026 Build Date : Wed Jul 29 14:57:16 2026
Install Reason : Installed as a dependency for another package * Build Date : Tue Oct 6 16:19:12 2026
Install Script : No Install Date : Sun Oct 4 00:00:00 2026
Validated By : Signature Install Reason : Installed as a dependency for another package
> Validated By : SHA-256 Sum Signature Install Script : No
> Repository : core Validated By : Signature
> Download Size : 5.12 MiB * Validated By : SHA-256 Sum Signature
EOF EOF
)" )"
log_has 'pacman -Qi -- glib2' log_has 'pacman -Qi -- glib2'
@@ -324,21 +324,21 @@ EOF
use_fakes pacman use_fakes pacman
run skup show su-dummy-b run skup show su-dummy-b
assert_success assert_success
assert_output --partial "$(printf '%s\n' 'Version : 1.0-1' '> Version : 2.0-1')" assert_output --partial "$(printf '%s\n' ' Version : 1.0-1' '* Version : 2.0-1')"
assert_line 'Required By : su-dummy-a' assert_line ' Required By : su-dummy-a'
refute_output --partial '3.0-1' refute_output --partial '3.0-1'
log_has 'pacman -Si -- su-dummy/su-dummy-b' log_has 'pacman -Si -- su-dummy/su-dummy-b'
# bash: in the sync list, but no -Si record - the output stays what it was (-Qi, then -Si) # bash: in the sync list, but no -Si record - the output stays what it was (-Qi, then -Si)
run skup show bash run skup show bash
assert_success assert_success
assert_line --index 0 'Name : bash' assert_line --index 0 ' Name : bash'
refute_line --partial '# lines starting with >' refute_line --partial '# lines starting with *'
# not installed: the candidate alone # not installed: the candidate alone
run skup show su-dummy-new run skup show su-dummy-new
assert_success assert_success
assert_line --index 0 '# not installed: this is the upstream candidate' assert_line --index 0 '# not installed: lines starting with * come from the upstream candidate (all of them)'
assert_line --index 1 'Repository : su-dummy' assert_line --index 1 '* Repository : su-dummy'
refute_line --partial '> ' refute_line --regexp '^ '
run skup show nope run skup show nope
assert_failure 1 assert_failure 1
} }
@@ -508,27 +508,27 @@ EOF
run skup show su-dummy-b run skup show su-dummy-b
assert_success assert_success
assert_output "$(cat <<'EOF' assert_output "$(cat <<'EOF'
# lines starting with > come from the upstream candidate: a differing value, or a field the installed record lacks # lines starting with * come from the upstream candidate: a differing value, or a field the installed record lacks
Name : su-dummy-b Name : su-dummy-b
Version : 1.0 Version : 1.0
> Version : 2.0 * Version : 2.0
Release : 1 Release : 1
Architecture : noarch Architecture : noarch
Size : 15 Size : 15
> Size : 6.5 k * Size : 6.5 k
Source : su-dummy-b-1.0-1.src.rpm Source : su-dummy-b-1.0-1.src.rpm
> Source : su-dummy-b-2.0-1.src.rpm * Source : su-dummy-b-2.0-1.src.rpm
Repository : @System Repository : @System
> Repository : su-dummy * Repository : su-dummy
Summary : skeptical-updater dummy su-dummy-b (v1.0) Summary : skeptical-updater dummy su-dummy-b (v1.0)
> Summary : skeptical-updater dummy su-dummy-b (v2.0) * Summary : skeptical-updater dummy su-dummy-b (v2.0)
License : MIT License : MIT
Description : Dummy package su-dummy-b version 1.0 for skeptical-updater tests. Description : Dummy package su-dummy-b version 1.0 for skeptical-updater tests.
: :
: Second paragraph of su-dummy-b 1.0 (blank line above on purpose). : Second paragraph of su-dummy-b 1.0 (blank line above on purpose).
> Description : Dummy package su-dummy-b version 2.0 for skeptical-updater tests. * Description : Dummy package su-dummy-b version 2.0 for skeptical-updater tests.
> : * :
> : Second paragraph of su-dummy-b 2.0 (blank line above on purpose). * : Second paragraph of su-dummy-b 2.0 (blank line above on purpose).
EOF EOF
)" )"
log_has 'dnf -q info --installed su-dummy-b' log_has 'dnf -q info --installed su-dummy-b'
@@ -536,9 +536,9 @@ EOF
# real almalinux:9 records: an identical multi-line Description once, "From repo" (installed only) kept # real almalinux:9 records: an identical multi-line Description once, "From repo" (installed only) kept
run skup show glibc run skup show glibc
assert_success assert_success
assert_output --partial "$(printf '%s\n' 'Release : 275.el9_8' '> Release : 283.el9_8')" assert_output --partial "$(printf '%s\n' ' Release : 275.el9_8' '* Release : 283.el9_8')"
assert_line 'From repo : baseos' assert_line ' From repo : baseos'
[[ $(grep -c '^Description : The glibc package contains' <<<"$output") -eq 1 ]] [[ $(grep -c '^ Description : The glibc package contains' <<<"$output") -eq 1 ]]
[[ $(grep -c 'Linux system will not function\.$' <<<"$output") -eq 1 ]] [[ $(grep -c 'Linux system will not function\.$' <<<"$output") -eq 1 ]]
} }
@@ -547,31 +547,31 @@ EOF
FAKE_DNF_MAJOR=5 run skup show su-dummy-b FAKE_DNF_MAJOR=5 run skup show su-dummy-b
assert_success assert_success
assert_output "$(cat <<'EOF' assert_output "$(cat <<'EOF'
# lines starting with > come from the upstream candidate: a differing value, or a field the installed record lacks # lines starting with * come from the upstream candidate: a differing value, or a field the installed record lacks
Name : su-dummy-b Name : su-dummy-b
Epoch : 0 Epoch : 0
Version : 1.0 Version : 1.0
> Version : 2.0 * Version : 2.0
Release : 1 Release : 1
Architecture : noarch Architecture : noarch
Installed size : 15.0 B Installed size : 15.0 B
Source : su-dummy-b-1.0-1.src.rpm Source : su-dummy-b-1.0-1.src.rpm
> Source : su-dummy-b-2.0-1.src.rpm * Source : su-dummy-b-2.0-1.src.rpm
From repository : <unknown> From repository : <unknown>
Summary : skeptical-updater dummy su-dummy-b (v1.0) Summary : skeptical-updater dummy su-dummy-b (v1.0)
> Summary : skeptical-updater dummy su-dummy-b (v2.0) * Summary : skeptical-updater dummy su-dummy-b (v2.0)
URL : URL :
License : MIT License : MIT
Description : Dummy package su-dummy-b version 1.0 for skeptical-updater tests. Description : Dummy package su-dummy-b version 1.0 for skeptical-updater tests.
: :
: Second paragraph of su-dummy-b 1.0 (blank line above on purpose). : Second paragraph of su-dummy-b 1.0 (blank line above on purpose).
> Description : Dummy package su-dummy-b version 2.0 for skeptical-updater tests. * Description : Dummy package su-dummy-b version 2.0 for skeptical-updater tests.
> : * :
> : Second paragraph of su-dummy-b 2.0 (blank line above on purpose). * : Second paragraph of su-dummy-b 2.0 (blank line above on purpose).
Vendor : <NULL> Vendor : <NULL>
> Vendor : * Vendor :
> Download size : 6.6 KiB * Download size : 6.6 KiB
> Repository : su-dummy * Repository : su-dummy
EOF EOF
)" )"
} }
@@ -580,16 +580,16 @@ EOF
use_fakes dnf rpm use_fakes dnf rpm
run skup show zsh run skup show zsh
assert_success assert_success
assert_line --index 0 '# not installed: this is the upstream candidate' assert_line --index 0 '# not installed: lines starting with * come from the upstream candidate (all of them)'
assert_line --index 1 'Name : zsh' assert_line --index 1 '* Name : zsh'
refute_line 'Available Packages' refute_line --partial 'Available Packages'
log_has 'dnf -q info --available zsh' log_has 'dnf -q info --available zsh'
# two installed records (e.g. two architectures): nothing to pair, the output stays dnf -q info # two installed records (e.g. two architectures): nothing to pair, the output stays dnf -q info
FAKE_DNF_INFO_TWICE=installed run skup show su-dummy-a FAKE_DNF_INFO_TWICE=installed run skup show su-dummy-a
assert_success assert_success
refute_line --partial '# lines starting with >' refute_line --partial '# lines starting with *'
assert_line 'Installed Packages' assert_line ' Installed Packages'
assert_line 'Available Upgrades' assert_line ' Available Upgrades'
log_has 'dnf -q info su-dummy-a' log_has 'dnf -q info su-dummy-a'
} }
@@ -673,8 +673,8 @@ EOF
use_fakes zypper rpm use_fakes zypper rpm
run skup show su-dummy-a run skup show su-dummy-a
assert_success assert_success
assert_line 'Information for package su-dummy-a:' assert_line ' Information for package su-dummy-a:'
refute_line --partial '# lines starting with >' refute_line --partial '# lines starting with *'
log_has 'zypper --non-interactive --quiet info --requires su-dummy-a' log_has 'zypper --non-interactive --quiet info --requires su-dummy-a'
run skup show nothing-like-this run skup show nothing-like-this
assert_failure 1 assert_failure 1
+1 -1
View File
@@ -43,7 +43,7 @@ setup() {
assert_line 'required by: su-dummy-a' assert_line 'required by: su-dummy-a'
run skup show su-dummy-a run skup show su-dummy-a
assert_success assert_success
assert_line 'Package: su-dummy-a' assert_line ' Package: su-dummy-a'
} }
@test "--dry-run works unprivileged: the refresh is printed, not run" { @test "--dry-run works unprivileged: the refresh is printed, not run" {