feat(info): show the install reason and drop the indentation

N? and `info PKG` now print three lines, none indented:

    PKG: <description>
    reason: explicitly installed | installed as a dependency | unknown | not installed
    required by: <installed dependents, comma separated> | nothing

The reason comes from a new <be>_reason backend function: apt-mark
showauto / showmanual (dpkg decides for a package in neither list), the
Install Reason of pacman -Qi, dnf repoquery --installed %{reason} (dnf4
and dnf5 words) and the Installed line of zypper info (rpm plus libzypp's
AutoInstalled when zypper is locked). A row whose old version is "-" is
not installed and the backend is not asked. The "(installed)" qualifier
moves to --help, README and DESIGN.

The rpm E2E fixture and e2e_reset now also mark su-dummy-a and su-dummy-c
as user-installed on dnf, which records plain rpm installs as "unknown",
so 1? and 2? show both reasons on all 12 distros. The verified commands
are recorded in discovery/{apt,pacman,rpm}.md.
This commit is contained in:
bryanpedini committed 2026-10-10 13:32:42 +02:00
1 parent 1354db6c39
commit 71d7f87aa3
21 files changed
+589 -48

No files matched your search

+17 -8
View File
@@ -41,7 +41,7 @@ commands:
trust PKG... add PKG and its installed dependency closure to the statefile
untrust PKG... remove rows for PKG (and rows whose reason is dep:PKG)
refresh recompute dependency closures for every 'user' root
info PKG one-line description + installed reverse dependencies (as N?)
info PKG description, install reason and installed dependents (as N?)
show PKG full native package information (as N??)
options:
@@ -80,7 +80,8 @@ Refreshing package lists... done.
<enter> upgrade | N? what is it | N?? full info | N- exclude this run | Nx purge now | N+ trust forever
> 2?
su-dummy-b: dummy package su-dummy-b for skeptical-updater tests
required by (installed): su-dummy-a
reason: installed as a dependency
required by: su-dummy-a
> 1+
trusted su-dummy-a (+1 dependency: su-dummy-b)
> 3-
@@ -126,7 +127,7 @@ listed prints an error, runs nothing, and the prompt comes back.
| token | action |
|-------|--------|
| empty line | proceed: run the native upgrade with the current exclusions |
| `N?` | one-line description and the installed packages that depend on it |
| `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 (`apt-cache show`, `pacman -Qi` + `-Si`, `dnf info`, `zypper info --requires`) |
| `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 |
@@ -220,7 +221,11 @@ upgrade` (Inst lines + the "kept back" block), `pacman -Qu` joined with `pacman
`zypper --xmlout list-updates`, or on Tumbleweed (as root) the real plan
`zypper --xmlout --no-refresh dist-upgrade --dry-run`. Reverse dependencies and dependency closures use
`apt-cache rdepends` / the dpkg database, `pacman -Qi`, and plain `rpm` queries on
dnf and zypper systems (see the discovery reports for why).
dnf and zypper systems (see the discovery reports for why). The install reason `N?` shows comes
from `apt-mark showauto` / `showmanual`, the `Install Reason` of `pacman -Qi`,
`dnf repoquery --installed --queryformat '%{reason}'` (packages installed with plain `rpm` are
`unknown` to dnf4, user-installed to dnf5) and the `Installed` line of `zypper info`
(`Yes (automatically)`; libzypp's `/var/lib/zypp/AutoInstalled` when zypper is locked).
## Testing
@@ -250,7 +255,8 @@ in front of symlinks to the few system tools it needs. The fakes replay the real
outputs recorded in `discovery/*.md` (data in `tests/unit/data/`), keep their state
(holds, purged packages) under the test's temp dir and log every invocation, so the
tests assert the exact commands issued. Covered: CLI parsing and exit codes, backend
detection and override, root handling and the sudo re-exec, every parser, the
detection and override, root handling and the sudo re-exec, every parser (install reasons
included: explicitly installed, dependency, unknown and not installed on each backend), the
statefile, the whole interactive grammar, the per-backend exclusion mechanics (apt
hold journal incl. crash recovery and SIGTERM, pacman `--ignore`, dnf `--exclude`,
zypper private lock file contents incl. drop-ins), the purge plans, the metadata freeze flags,
@@ -273,9 +279,12 @@ plus `su-dummy-orphan`, an unrelated automatically-installed package nothing dep
with the distro's own repositories disabled (apt `SU_FIXTURE_OFFLINE=1`, pacman
`FIXTURE_ISOLATE=1`, rpm `SU_FIXTURE_ISOLATE=1` after the build tools are installed),
so the review list is exactly the three dummies and the result does not depend on
mirror state. Then `tests/e2e/*.bats` exercise the real package manager: banner,
list, `N?`, `N??`, `N+` and the statefile, `trust`/`untrust`/`refresh`, excluding
`su-dummy-b` (with and without a pre-existing hold/lock/IgnorePkg/exclude on
mirror state. `su-dummy-b` is recorded as installed as a dependency on every backend (pacman
`--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??`, `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
dependency, full upgrade, `Nx` (refused for su-dummy-b, which su-dummy-a needs; su-dummy-a
taking its dependency su-dummy-b along, shown in the prompt, and the unrelated orphan surviving),