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 |
| `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 |
| `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) |
@@ -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
(`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`,
`pacman -Si` from the repository pacman would use, `dnf info --upgrades`) comes the candidate's line,
prefixed with `>`; fields only the candidate has (download size, checksums, repository) follow at the
end the same way, and fields only the installed record has (install reason, conffiles,
`Required By`) stay as they are. Identical fields are printed once; multi-line values (descriptions, pacman
`Optional Deps`, apt continuation lines) are compared as blocks and, when they differ, every line of
the candidate's block gets the `>`. A package that is not installed shows the candidate alone. 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. On Debian 12, with
`su-dummy-b` 1.0 installed and 2.0 available:
it. Every other line starts with a two-character gutter, so keys and colons line up: two spaces
for the installed record, `* ` for the upstream candidate. Right after each field whose value
differs in the upstream candidate (`apt-cache show`, `pacman -Si` from the repository pacman would
use, `dnf info --upgrades`) comes the candidate's line, marked `* `; fields only the installed record
has (install reason, conffiles, `Required By`) stay as they are, and identical fields are printed
once. Fields only the candidate has (download size, checksums, repository) follow at the end the same
way - on pacman next to their installed counterpart instead (`Repository` after `Version`,
`Download Size` after `Installed Size`; for the AUR `AUR URL` after `URL`, `Make Deps`/`Check Deps`
after `Optional Deps`, `Maintainer` after `Packager`, `Last Modified` and `First Submitted` after
`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??
# lines starting with > come from the upstream candidate: a differing value, or a field the installed record lacks
Package: su-dummy-b
Status: install ok installed
Priority: optional
Section: misc
Maintainer: Skeptical Updater Fixture <su-fixture@example.invalid>
Architecture: all
Version: 1.0
> Version: 2.0
Conffiles:
/etc/su-dummy/su-dummy-b.conf 607e2d3a119dfe832d937ce579ef1079
Description: dummy package su-dummy-b for skeptical-updater tests
This package only exists to exercise the package manager.
.
It ships one data file and one conffile (version 1.0).
> Description: dummy package su-dummy-b for skeptical-updater tests
> This package only exists to exercise the package manager.
> .
> It ships one data file and one conffile (version 2.0).
> Description-md5: 27e6732d8f16131f7512e1d4f1855cc3
> Filename: ./su-dummy-b_2.0_all.deb
> Size: 824
> MD5sum: 62274690c5324af5419515e9f430226b
> SHA256: e1c87480693d98770356f1dc55616dc416f7e3c3f71dc3967e94dec77bb36c16
# lines starting with * come from the upstream candidate: a differing value, or a field the installed record lacks
Package: su-dummy-b
Status: install ok installed
Priority: optional
Section: misc
Maintainer: Skeptical Updater Fixture <su-fixture@example.invalid>
Architecture: all
Version: 1.0
* Version: 2.0
Conffiles:
/etc/su-dummy/su-dummy-b.conf 607e2d3a119dfe832d937ce579ef1079
Description: dummy package su-dummy-b for skeptical-updater tests
This package only exists to exercise the package manager.
.
It ships one data file and one conffile (version 1.0).
* Description: dummy package su-dummy-b for skeptical-updater tests
* This package only exists to exercise the package manager.
* .
* It ships one data file and one conffile (version 2.0).
* Description-md5: 27e6732d8f16131f7512e1d4f1855cc3
* Filename: ./su-dummy-b_2.0_all.deb
* Size: 824
* MD5sum: 62274690c5324af5419515e9f430226b
* SHA256: e1c87480693d98770356f1dc55616dc416f7e3c3f71dc3967e94dec77bb36c16
```
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).
- **`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
AUR record (`paru -Sia` / `yay -Sia`), `>` lines as usual: `> Version`, `> Repository : aur`,
`> Maintainer`, `> Votes`, ... (`show PKG` does the same for a package in no repository).
AUR record (`paru -Sia` / `yay -Sia`), `*` lines as usual: `* Version` followed by
`* 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`.
- **`<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
@@ -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
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
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,
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,
@@ -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
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
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,
reason and `required by:` of both), `N??` (su-dummy-b's installed 1.0 record with `* Version`, and
`* Repository` on pacman and dnf, every line in the gutter; zypper's plain `zypper info`), `N+` and
the statefile,
`trust`/`untrust`/`refresh`,
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