Compare commits
2
Commits
0.1.1
...
5f90c24b8d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f90c24b8d | ||
|
|
33ebf89748 |
No files matched your search
@@ -130,8 +130,13 @@ package (pacman backend with paru or yay, see below) and a space on every other
|
||||
|
||||
One line, whitespace-separated tokens; `N` is the number in brackets. Numbers are
|
||||
stable for the whole session (rows that leave the list leave a gap). Every token
|
||||
of a line is validated first: a line with an unknown token or a number that is not
|
||||
listed prints an error, runs nothing, and the prompt comes back.
|
||||
of a line is checked for syntax first: an unknown token rejects the line. A number
|
||||
or package name that is not listed prints an error and the remaining commands still run.
|
||||
For `?` and `??`, a listed package name can replace the number, for example
|
||||
`su-dummy-a?` or `su-dummy-a??`. A digits-only reference is interpreted as a number.
|
||||
Names are restricted to these information commands to avoid unnecessary parsing
|
||||
complexity: package names may contain `-` or end in `x`, which conflicts with the
|
||||
other command suffixes.
|
||||
|
||||
| token | action |
|
||||
|-------|--------|
|
||||
@@ -147,7 +152,10 @@ listed prints an error, runs nothing, and the prompt comes back.
|
||||
| `h`, `?` | help |
|
||||
| end of input | abort without upgrading (exit 2) |
|
||||
|
||||
Several tokens may share a line (`1? 3- 4+`); they run left to right. With `--all`
|
||||
Several tokens may share a line (`1? 3- 4+`); they run left to right. Consecutive
|
||||
`N+` commands are processed together: selected packages required by another selected
|
||||
package are recorded as dependencies, regardless of their order. `*+` works the same
|
||||
way. With `--all`
|
||||
trusted packages are listed too, marked with `=`. When purges leave nothing to upgrade the
|
||||
session ends by itself (exit 0).
|
||||
|
||||
|
||||
+10
-2
@@ -125,6 +125,14 @@ Refreshing package lists... done.
|
||||
```
|
||||
|
||||
Command grammar (one line, whitespace-separated tokens, each `N` is the number in brackets):
|
||||
Unknown tokens reject the line; nonexistent numbers report an error and are skipped.
|
||||
For `?` and `??`, the exact name of a listed package may replace `N`; a digits-only
|
||||
reference is treated as a number. Missing or no longer listed names are reported and
|
||||
skipped. Other commands accept only numbers: package names can contain `-` or end in
|
||||
`x`, and limiting name references to information commands avoids unnecessary parsing
|
||||
complexity.
|
||||
Consecutive `N+` commands and `*+` trust the selected dependency roots, recording
|
||||
other selected packages in their closures as dependencies regardless of input order.
|
||||
|
||||
| token | action |
|
||||
|--------|---------------------------------------------------------------------------|
|
||||
@@ -153,8 +161,8 @@ On AUR rows `N?`, `N+` and `Nx` act through pacman like on any installed package
|
||||
`pacman -Qi` with `<helper> -Sia PKG`; `N-` excludes the row through the helper's `--ignore`.
|
||||
|
||||
Numbers are stable for the whole session (removed rows leave a gap, never renumber).
|
||||
All tokens of a line are validated first: an unknown token or a number that is not listed
|
||||
prints an error, runs nothing of that line and re-prompts. EOF on stdin aborts without
|
||||
Syntax is validated first; an unknown token rejects the line. Numbers that are not listed
|
||||
print an error and are skipped while the remaining commands run. EOF on stdin aborts without
|
||||
upgrading (exit 2) so a piped run can never upgrade by accident unless it sends an empty
|
||||
line. When purges leave no row at all, the session ends ("Nothing left to upgrade.", exit 0).
|
||||
|
||||
|
||||
+61
-19
@@ -6,7 +6,7 @@
|
||||
# and zypper: list what would be upgraded, interrogate every package, remember the
|
||||
# ones you trust, then run the native upgrade (with per-run exclusions).
|
||||
#
|
||||
# Version: 0.1.0
|
||||
# Version: 0.3.0
|
||||
#
|
||||
# Specification: docs/DESIGN.md. Single file, bash >= 4.3, no runtime dependency
|
||||
# beyond the distro's package manager and coreutils/sed/grep (awk only on the apt
|
||||
@@ -23,7 +23,7 @@ export LC_ALL=C DEBIAN_FRONTEND=noninteractive
|
||||
# never read from or written to a file (no HISTFILE; `history -r`/`-w`/`-a` are never used).
|
||||
unset HISTFILE
|
||||
|
||||
readonly SU_VERSION=0.1.0
|
||||
readonly SU_VERSION=0.3.0
|
||||
readonly SU_PROG=skeptical-updater
|
||||
readonly SU_STATE_HEADER='# skeptical-updater trusted packages. Columns: backend package reason added(UTC ISO-8601)'
|
||||
readonly SU_PROMPT_HELP='<enter> upgrade | N? what is it | N?? full info | N- exclude this run | Nx purge now | N+ trust forever'
|
||||
@@ -114,6 +114,7 @@ interactive commands (one line, whitespace-separated tokens, N = number in brack
|
||||
that depend on it - installed packages only
|
||||
N?? full native info: the installed record; lines starting with '*' come
|
||||
from the upstream candidate (differing values, fields it adds)
|
||||
For N? and N??, a listed package name may be used instead of N.
|
||||
N- exclude from this run (toggle); shown with a '-' marker
|
||||
Nx purge it now with its now-unneeded dependencies (shown, asks y/N); refused
|
||||
when other installed packages depend on it, like pacman -Rns
|
||||
@@ -138,6 +139,7 @@ session_help() {
|
||||
<enter> upgrade now, skipping the rows marked '-'
|
||||
N? what is it: description, install reason + installed packages that need it
|
||||
N?? full native info: installed record, '*' lines from the upstream candidate
|
||||
A listed package name may replace N for ? and ?? (e.g. okular?).
|
||||
N- exclude N from this run (toggle)
|
||||
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
|
||||
@@ -2712,9 +2714,28 @@ session_purge() {
|
||||
}
|
||||
|
||||
session_trust() { # session_trust IDX...
|
||||
local i now
|
||||
now=$(now_utc)
|
||||
local i j d now covered
|
||||
local -a selected=() roots=()
|
||||
local -A seen=() reach=()
|
||||
for i in "$@"; do
|
||||
[[ -z ${seen[$i]+x} ]] || continue
|
||||
seen[$i]=1
|
||||
selected+=("$i")
|
||||
done
|
||||
for i in "${selected[@]}"; do
|
||||
closure_of "${R_NAME[i]}"
|
||||
for d in ${CLOSURE[@]+"${CLOSURE[@]}"}; do reach["$i,$d"]=1; done
|
||||
done
|
||||
for i in "${selected[@]}"; do
|
||||
covered=0
|
||||
for j in "${selected[@]}"; do
|
||||
[[ $i != "$j" && -n ${reach[$j,${R_NAME[i]}]+x} ]] || continue
|
||||
if [[ -z ${reach[$i,${R_NAME[j]}]+x} ]] || ((j < i)); then covered=1; break; fi
|
||||
done
|
||||
((covered)) || roots+=("$i")
|
||||
done
|
||||
now=$(now_utc)
|
||||
for i in "${roots[@]}"; do
|
||||
trust_pkg "${R_NAME[i]}" "$now"
|
||||
R_EXCL[i]=0
|
||||
done
|
||||
@@ -2747,24 +2768,19 @@ session_toggle_all() {
|
||||
SESSION_PROCEED=0
|
||||
UPGRADE_FAILED_MSG= # set by <be>_upgrade when the failing step is not the package manager's own
|
||||
|
||||
# session_line LINE: validate every token first (a typo never executes half a line), then run them
|
||||
session_line() {
|
||||
local tok n op i
|
||||
local -a toks=()
|
||||
local tok n op i name
|
||||
local -a toks=() batch=()
|
||||
local -A initial=() named=()
|
||||
local re='^([0-9]{1,6})(\?\?|\?|-|x|\+)$'
|
||||
local name_re='^([A-Za-z0-9_+@][A-Za-z0-9_+.@:~-]*)(\?\?|\?)$'
|
||||
read -r -a toks <<<"$1" || true
|
||||
if ((${#toks[@]} == 0)); then
|
||||
SESSION_PROCEED=1
|
||||
return 0
|
||||
fi
|
||||
for tok in "${toks[@]}"; do
|
||||
if [[ $tok =~ $re ]]; then
|
||||
n=$((10#${BASH_REMATCH[1]}))
|
||||
if ! num_row "$n"; then
|
||||
err "no package [$n] in the list (l to redraw)"
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
if [[ ! $tok =~ $re && ! $tok =~ $name_re ]]; then
|
||||
case $tok in
|
||||
'*+' | '*-' | l | q | h | '?') ;;
|
||||
*)
|
||||
@@ -2774,15 +2790,26 @@ session_line() {
|
||||
esac
|
||||
fi
|
||||
done
|
||||
for ((n = 1; n <= N_MAX; n++)); do
|
||||
if num_row "$n"; then
|
||||
initial[$n]=$REPLY
|
||||
named[${R_NAME[REPLY]}]=$REPLY
|
||||
fi
|
||||
done
|
||||
for tok in "${toks[@]}"; do
|
||||
if [[ $tok =~ $re ]]; then
|
||||
n=$((10#${BASH_REMATCH[1]}))
|
||||
op=${BASH_REMATCH[2]}
|
||||
if ! num_row "$n"; then
|
||||
err "package [$n] has left the list; ignoring the rest of the line"
|
||||
return 0
|
||||
if [[ -z ${initial[$n]+x} ]] || ((R_GONE[${initial[$n]:-0}])); then
|
||||
err "no package [$n] in the list (l to redraw)"
|
||||
continue
|
||||
fi
|
||||
i=$REPLY
|
||||
i=${initial[$n]}
|
||||
if [[ $op == + ]]; then
|
||||
batch+=("$i")
|
||||
continue
|
||||
fi
|
||||
if ((${#batch[@]})); then session_trust "${batch[@]}"; batch=(); fi
|
||||
case $op in
|
||||
'?') print_info "${R_NAME[i]}" "${R_OLD[i]}" || true ;;
|
||||
'??') print_full_info "${R_NAME[i]}" "${R_OLD[i]}" || true ;;
|
||||
@@ -2802,9 +2829,23 @@ session_line() {
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
+) session_trust "$i" ;;
|
||||
esac
|
||||
elif [[ $tok =~ $name_re ]]; then
|
||||
name=${BASH_REMATCH[1]}
|
||||
op=${BASH_REMATCH[2]}
|
||||
if [[ -z ${named[$name]+x} ]] || ((R_GONE[${named[$name]:-0}])); then
|
||||
err "no package '$name' in the list (l to redraw)"
|
||||
continue
|
||||
fi
|
||||
i=${named[$name]}
|
||||
if ((${#batch[@]})); then session_trust "${batch[@]}"; batch=(); fi
|
||||
if [[ $op == '?' ]]; then
|
||||
print_info "${R_NAME[i]}" "${R_OLD[i]}" || true
|
||||
else
|
||||
print_full_info "${R_NAME[i]}" "${R_OLD[i]}" || true
|
||||
fi
|
||||
else
|
||||
if ((${#batch[@]})); then session_trust "${batch[@]}"; batch=(); fi
|
||||
case $tok in
|
||||
'*+')
|
||||
local -a idx=()
|
||||
@@ -2823,6 +2864,7 @@ session_line() {
|
||||
esac
|
||||
fi
|
||||
done
|
||||
if ((${#batch[@]})); then session_trust "${batch[@]}"; fi
|
||||
}
|
||||
|
||||
cmd_interactive() {
|
||||
|
||||
@@ -101,6 +101,54 @@ UPGRADE='apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--forc
|
||||
log_has 'apt-cache show --no-all-versions su-dummy-a'
|
||||
}
|
||||
|
||||
@test "listed package names support both information commands alongside numeric commands" {
|
||||
run skup --no-refresh <<<$'su-dummy-a? 2? su-dummy-b?? 3-\nq'
|
||||
assert_success
|
||||
assert_line 'su-dummy-a: dummy package su-dummy-a for skeptical-updater tests'
|
||||
assert_line 'su-dummy-b: dummy package su-dummy-b for skeptical-updater tests'
|
||||
assert_line ' Package: su-dummy-b'
|
||||
assert_line 'excluded from this run: su-dummy-c'
|
||||
log_has 'dpkg-query -s -- su-dummy-b'
|
||||
}
|
||||
|
||||
@test "unknown or hidden package names report an error and do not stop later commands" {
|
||||
write_state "$HEADER" "apt${T}su-dummy-a${T}user${T}2026-01-01T00:00:00Z"
|
||||
run skup --no-refresh <<<$'missing? su-dummy-a?? 2?\nq'
|
||||
assert_success
|
||||
assert_output --partial "no package 'missing' in the list"
|
||||
assert_output --partial "no package 'su-dummy-a' in the list"
|
||||
assert_line 'su-dummy-c: dummy package su-dummy-c for skeptical-updater tests'
|
||||
}
|
||||
|
||||
@test "names work for trusted rows shown with --all and new dependencies" {
|
||||
write_state "$HEADER" "apt${T}su-dummy-a${T}user${T}2026-01-01T00:00:00Z"
|
||||
run skup --no-refresh --all <<<$'su-dummy-a?\nq'
|
||||
assert_success
|
||||
assert_line 'su-dummy-a: dummy package su-dummy-a for skeptical-updater tests'
|
||||
FAKE_APT_SIM=real run skup --no-refresh <<<$'su-dummy-newdep? su-dummy-newdep??\nq'
|
||||
assert_success
|
||||
assert_line 'reason: not installed'
|
||||
log_lacks '^dpkg-query -s -- su-dummy-newdep'
|
||||
log_has 'apt-cache show --no-all-versions su-dummy-newdep'
|
||||
}
|
||||
|
||||
@test "a package removed during a line cannot be queried by name" {
|
||||
run skup --no-refresh <<<$'3x su-dummy-c? 2?\ny\nq'
|
||||
assert_success
|
||||
assert_output --partial "no package 'su-dummy-c' in the list"
|
||||
assert_line 'su-dummy-b: dummy package su-dummy-b for skeptical-updater tests'
|
||||
}
|
||||
|
||||
@test "package names are not accepted for trust, exclude or purge" {
|
||||
for token in su-dummy-a+ su-dummy-a- su-dummy-ax; do
|
||||
run skup --no-refresh <<<"$token"$'\nq'
|
||||
assert_success
|
||||
assert_output --partial "unknown command '$token'"
|
||||
[[ ! -e $STATE_FILE ]]
|
||||
log_lacks '^apt-get -y purge'
|
||||
done
|
||||
}
|
||||
|
||||
@test "N?? on a new dependency (old version -) never looks for an installed record" {
|
||||
FAKE_APT_SIM=real run skup --no-refresh <<<$'9??\nq'
|
||||
assert_success
|
||||
@@ -292,11 +340,49 @@ UPGRADE='apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--forc
|
||||
run skup --no-refresh <<<$'*+\nl\nq'
|
||||
assert_success
|
||||
assert_line 'trusted su-dummy-a (+1 dependency: su-dummy-b)'
|
||||
assert_line 'trusted su-dummy-b'
|
||||
assert_line 'trusted su-dummy-c'
|
||||
assert_line '3 upgradable, 3 trusted (hidden, use --all to show), 0 to review:'
|
||||
run cut -f2,3 "$STATE_FILE"
|
||||
assert_line "su-dummy-b${T}user"
|
||||
assert_line "su-dummy-b${T}dep:su-dummy-a"
|
||||
}
|
||||
|
||||
@test "consecutive trust commands keep dependencies under their selected root in either order" {
|
||||
for line in '1+ 2+ 3+' '2+ 3+ 1+'; do
|
||||
rm -rf "$STATE"
|
||||
run skup --no-refresh <<<"$line"$'\nq'
|
||||
assert_success
|
||||
assert_line 'trusted su-dummy-a (+1 dependency: su-dummy-b)'
|
||||
assert_line 'trusted su-dummy-c'
|
||||
refute_output --partial 'no package ['
|
||||
run cut -f2,3 "$STATE_FILE"
|
||||
assert_line "su-dummy-a${T}user"
|
||||
assert_line "su-dummy-b${T}dep:su-dummy-a"
|
||||
assert_line "su-dummy-c${T}user"
|
||||
done
|
||||
}
|
||||
|
||||
@test "invalid numbers do not stop other commands on the line" {
|
||||
run skup --no-refresh <<<$'9+ 2- 0? 3+\nq'
|
||||
assert_success
|
||||
assert_output --partial 'no package [9] in the list'
|
||||
assert_output --partial 'no package [0] in the list'
|
||||
assert_line 'excluded from this run: su-dummy-b'
|
||||
assert_line 'trusted su-dummy-c'
|
||||
}
|
||||
|
||||
@test "a package removed by a purge does not stop later commands" {
|
||||
FAKE_APT_AUTO_ORPHANS=su-dummy-a:su-dummy-b run skup --no-refresh <<<$'1x 2? 3-\ny\nq'
|
||||
assert_success
|
||||
assert_output --partial 'no package [2] in the list'
|
||||
assert_line 'excluded from this run: su-dummy-c'
|
||||
}
|
||||
|
||||
@test "trusting a dependency and its root with --all keeps the dependency implicit" {
|
||||
run skup --no-refresh --all <<<$'2+ 1+\nq'
|
||||
assert_success
|
||||
run cut -f2,3 "$STATE_FILE"
|
||||
assert_line "su-dummy-a${T}user"
|
||||
assert_line "su-dummy-b${T}dep:su-dummy-a"
|
||||
}
|
||||
|
||||
@test "*- excludes every listed package; *- again re-includes them" {
|
||||
|
||||
Reference in new issue
Block a user