feat(show): merge the installed record with the upstream candidate

N?? and `show PKG` now print the installed record in the package
manager's own key/value layout, after one line explaining the format:

    # lines starting with > come from the upstream candidate: a differing
    value, or a field the installed record lacks

After each field whose candidate value differs comes the candidate's
line prefixed with "> "; fields only the candidate has are appended the
same way; installed-only fields stay; identical fields print once.
Multi-line values (pacman Optional Deps, dnf Description, RFC-822
continuation lines) are compared as blocks and a differing candidate
block gets "> " on every line. A package that is not installed shows the
candidate alone ("# not installed: this is the upstream candidate").

Merged where the backend gives comparable records for both sides:
- pacman: -Qi vs -Si REPO/PKG, REPO = the first repository -Sl lists
- apt: dpkg-query -s (Status, Conffiles, the long description; richer
  than apt-cache show PKG=<installed>, which gives the archive record
  when that version is still listed) vs apt-cache show --no-all-versions;
  Description-xx pairs with Description and a one-line candidate
  Description equal to the installed first line counts as identical
  (the archives ship the long text in Translation files)
- dnf: info --installed vs info --upgrades (--available lists other
  architectures first and nothing for an up-to-date package); not
  installed: --available; dnf5's differently padded keys are re-padded
zypper keeps `zypper info --requires`: rpm -qi and zypper info share
almost no keys and zypper cannot print the installed record itself.
No candidate, or not exactly one record per side, keeps the old output.

Formats verified in containers and recorded in discovery/*.md. Unit
tests replay recorded pacman, apt, dnf4 and dnf5 records; the E2E suite
checks 2?? on su-dummy-b on all 12 distros.
This commit is contained in:
bryanpedini committed 2026-10-10 14:24:39 +02:00
1 parent 296c77e319
commit 875c8294bf
30 files changed
+857 -45

No files matched your search

+37
View File
@@ -743,3 +743,40 @@ no-such-pkg package 'no-such-pkg' not found. rc 104
* Cross-check: the flag is exactly membership in `/var/lib/zypp/AutoInstalled` (bare package names, plus `pattern:...` lines and `#` comments; 0644 root, world-readable). Deleting `su-dummy-b` from it turns the line into `Yes` immediately; an `rpm -Uvh --replacepkgs` reinstall keeps it; `zypper install su-dummy-a` (rpm -e'd first) installs b with `Yes (automatically)` and adds it to the file; naming b on the command line (`zypper update su-dummy-a su-dummy-b`) drops the flag (b becomes user-installed).
* Works as `nobody`. As root, while another process owns the zypp lock (`/run/zypp.pid` with a live pid), `zypper info` exits 7 ("System management is locked by the application with pid N (sleep)") - as `nobody` it still answers. Fallback in that case (and for exit 104): `rpm -q PKG` fails -> not installed; else membership in AutoInstalled -> dependency / explicit; unreadable -> unknown.
* Fixture: a/c (plain rpm, not in AutoInstalled) are `Yes`, b is `Yes (automatically)` with `SU_FIXTURE_B_AUTO=1`; `e2e_reset` re-adds b to AutoInstalled. No change needed.
## 15. Full info (`N??` / `show`): installed record merged with the candidate
Verified 2026-10-10, fixture `SU_FIXTURE_ISOLATE=1 SU_FIXTURE_ORPHAN=1 SU_FIXTURE_B_AUTO=1`, `LC_ALL=C`, plus real packages on almalinux:9 (`glibc` 2.34-275.el9_8 installed, 283.el9_8 in baseos; `bash`, up to date; `zsh`, not installed).
### 15.1 DNF: `dnf -q info --installed PKG` vs `dnf -q info --upgrades PKG`
dnf4 (almalinux:9 4.14.0, almalinux:10 4.20.0 - identical apart from sizes): one heading line (`Installed Packages`, `Available Upgrades`, `Available Packages`), then `Key : value` records, `:` at column 14 on both sides, `Description` continued by ` : text` lines (an empty paragraph is ` : `), a blank line after each record; `Size : 15 ` keeps trailing blanks. Same keys on both sides (Name, Version, Release, Architecture, Size, Source, Repository, `From repo` (installed only, when dnf installed it), Summary, URL, License, Description): Version/Release, Size (installed size vs download size, same key), Source, Repository (`@System` vs the repo id) and changed Summary/Description differ.
dnf5 (fedora:latest, 5.4.3.0): headings `Installed packages` / `Available upgrades` / `Available packages`; installed keys Name, Epoch, Version, Release, Architecture, `Installed size`, Source, `From repository`, Summary, URL, License, Description, Vendor (`:` at column 17); the candidate has `Download size`, `Installed size`, `Repository` instead of `From repository` (`:` at column 16). `Installed size` is the same value with different padding; `Vendor` is `<NULL>` installed and empty in the repo for the fixture.
* `--upgrades`, not `--available`, for an installed package: on almalinux:9 `info --available glibc` prints the **i686** build first and then x86_64 (both not installed), and nothing for the up-to-date bash (`Error: No matching Packages to list`, rc 1); `--upgrades glibc` prints exactly the x86_64 candidate. Not installed (`zsh`): `--installed` fails, `--available` prints the record.
* Both rc 1 with no match (dnf4 `Error: No matching Packages to list`, dnf5 `No matching packages to list`, stderr).
### 15.2 ZYPPER: not merged
```
$ rpm -qi su-dummy-b $ zypper -n --no-refresh info su-dummy-b
Name : su-dummy-b Information for package su-dummy-b:
Version : 1.0 -----------------------------------
Release : 1 Repository : su-dummy
Architecture: noarch Name : su-dummy-b
Install Date: Sat Oct 10 12:02:01 2026 Version : 2.0-1
Group : Unspecified Arch : noarch
Size : 15 Vendor :
License : MIT Installed Size : 15 B
Signature : (none) Installed : Yes (automatically)
Source RPM : su-dummy-b-1.0-1.src.rpm Status : out-of-date (version 1.0-1 installed)
Build Date : Sat Oct 10 12:02:01 2026 Source package : su-dummy-b-2.0-1.src
Build Host : 17756d8ad56d Summary : skeptical-updater dummy su-dummy-b (v2.0)
Summary : skeptical-updater ... Description :
Description : Dummy package su-dummy-b version 2.0 ...
Dummy package su-dummy-b version 1.0 ...
Distribution: (none)
```
* Only Name and Summary pair directly. Version (version vs version-release), Arch/Architecture, Size (bytes) vs `Installed Size` (`15 B`), `Source RPM` vs `Source package` (different file names), the description layout (rpm: unindented lines after `Description :`; zypper: indented 4) would all need translating, and zypper cannot print the installed record in its own layout (`zypper info -r @System PKG`: `Repository '@System' not found by its alias, number, or URI.`, rc 3). `N??` stays `zypper info --requires` (opensuse/tumbleweed, zypper 1.14.101).