|
|
|
@@ -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.3.1
|
|
|
|
|
# Version: 0.4.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.3.1
|
|
|
|
|
readonly SU_VERSION=0.4.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'
|
|
|
|
@@ -118,6 +118,7 @@ interactive commands (one line, whitespace-separated tokens, N = number in brack
|
|
|
|
|
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
|
|
|
|
|
consecutive Nx commands are purged together after one confirmation
|
|
|
|
|
N+ trust it forever (with its installed dependency closure)
|
|
|
|
|
*+ / *- apply + / - to every listed package
|
|
|
|
|
l redraw the list
|
|
|
|
@@ -144,6 +145,7 @@ session_help() {
|
|
|
|
|
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
|
|
|
|
|
pacman -Rns; purge the dependents first
|
|
|
|
|
consecutive Nx commands are purged together after one confirmation
|
|
|
|
|
N+ trust N (and its installed dependencies) forever
|
|
|
|
|
*+ *- trust / exclude every listed package (*- again re-includes them)
|
|
|
|
|
l redraw the list (a '*' after the number marks an AUR package)
|
|
|
|
@@ -972,20 +974,21 @@ apt_closure() {
|
|
|
|
|
# is system-wide (it also takes pre-existing orphans, e.g. old kernels: verified), so the
|
|
|
|
|
# orphans that were already autoremovable before are subtracted and the rest named explicitly.
|
|
|
|
|
apt_purge_plan() {
|
|
|
|
|
local pkg=$1 pre sim w name rest
|
|
|
|
|
local -A before=()
|
|
|
|
|
local -a args=("$pkg")
|
|
|
|
|
local pre sim w name rest
|
|
|
|
|
local -A before=() requested=()
|
|
|
|
|
local -a args=("$@")
|
|
|
|
|
for name in "$@"; do requested[$name]=1; done
|
|
|
|
|
pre=$(apt-get -s autoremove </dev/null 2>/dev/null) || pre=''
|
|
|
|
|
while read -r w name rest; do
|
|
|
|
|
[[ $w == Remv || $w == Purg ]] && before[$name]=1
|
|
|
|
|
done <<<"$pre"
|
|
|
|
|
sim=$(apt-get -s purge --auto-remove "$pkg" </dev/null 2>&1) || {
|
|
|
|
|
sim=$(apt-get -s purge --auto-remove "$@" </dev/null 2>&1) || {
|
|
|
|
|
printf '%s\n' "$sim" >&2
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
while read -r w name rest; do
|
|
|
|
|
[[ $w == Remv || $w == Purg ]] || continue
|
|
|
|
|
[[ -z ${before[$name]+x} && $name != "$pkg" ]] && args+=("$name")
|
|
|
|
|
[[ -z ${before[$name]+x} && -z ${requested[$name]+x} ]] && args+=("$name")
|
|
|
|
|
done <<<"$sim"
|
|
|
|
|
sim=$(apt-get -s purge "${args[@]}" </dev/null 2>&1) || {
|
|
|
|
|
printf '%s\n' "$sim" >&2
|
|
|
|
@@ -1507,7 +1510,7 @@ pacman_closure() {
|
|
|
|
|
# On refusal pacman explains on stdout (":: removing X breaks dependency ..."): show all of it.
|
|
|
|
|
pacman_purge_plan() {
|
|
|
|
|
local out rc=0 l
|
|
|
|
|
out=$(COLUMNS=0 pacman -Rs --print --print-format '%n' -- "$1" </dev/null 2>&1) || rc=$?
|
|
|
|
|
out=$(COLUMNS=0 pacman -Rs --print --print-format '%n' -- "$@" </dev/null 2>&1) || rc=$?
|
|
|
|
|
if ((rc)); then
|
|
|
|
|
printf '%s\n' "$out" >&2
|
|
|
|
|
return "$rc"
|
|
|
|
@@ -1908,7 +1911,7 @@ dnf_purge_plan() {
|
|
|
|
|
local out line in=0 wrapped=0
|
|
|
|
|
local -a f=()
|
|
|
|
|
local arch_re='^(noarch|x86_64|x86_64_v[0-9]|aarch64|i[3-6]86|ppc64le|ppc64|s390x|armv7hl|armv7l|riscv64|loongarch64|src)$'
|
|
|
|
|
out=$("$DNF" --assumeno remove "$1" </dev/null 2>&1) || true
|
|
|
|
|
out=$("$DNF" --assumeno remove "$@" </dev/null 2>&1) || true
|
|
|
|
|
if [[ $out != *'Transaction Summary'* ]]; then
|
|
|
|
|
if [[ $out == *'Nothing to do'* ]]; then return 0; fi
|
|
|
|
|
printf '%s\n' "$out" >&2
|
|
|
|
@@ -2091,7 +2094,7 @@ zypper_reason() {
|
|
|
|
|
}
|
|
|
|
|
zypper_purge_plan() {
|
|
|
|
|
local xml rc=0 sec name rest
|
|
|
|
|
xml=$(zypper --non-interactive --no-refresh --xmlout remove --dry-run --clean-deps "$1" </dev/null 2>&1) || rc=$?
|
|
|
|
|
xml=$(zypper --non-interactive --no-refresh --xmlout remove --dry-run --clean-deps "$@" </dev/null 2>&1) || rc=$?
|
|
|
|
|
if ((rc)); then
|
|
|
|
|
[[ $rc != 104 ]] || say "zypper: no installed package provides '$1' (exit 104)" >&2
|
|
|
|
|
((rc == 104)) || printf '%s\n' "$xml" >&2
|
|
|
|
@@ -2631,66 +2634,103 @@ num_row() { # num_row N -> $REPLY = row index of a listed row, rc 1 if N is not
|
|
|
|
|
REPLY=${N_IDX[n]}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# session_purge IDX: pacman's -Rns principle on every backend - a package that other installed
|
|
|
|
|
# session_purge IDX...: pacman's -Rns principle on every backend - a package that other installed
|
|
|
|
|
# packages still depend on is never purged (they are listed, as N? does; nothing cascades);
|
|
|
|
|
# otherwise the package goes with the dependencies it alone needed, as the y/N prompt lists.
|
|
|
|
|
session_purge() {
|
|
|
|
|
local i=$1 name=${R_NAME[$1]} rc=0 out line p prompt rd d
|
|
|
|
|
local -a plan=() others=() extra=()
|
|
|
|
|
local -A still=() gone=() mine=()
|
|
|
|
|
local i name rd out rc=0 p prompt idx dep changed
|
|
|
|
|
local -a targets=() target_names=() total_plan=() total_others=() extra=() rdeps=()
|
|
|
|
|
local -A purge_selected=() rejected=() reverse=() gone=() allowed=() still=()
|
|
|
|
|
local line
|
|
|
|
|
|
|
|
|
|
for idx in "$@"; do purge_selected[${R_NAME[idx]}]=$idx; done
|
|
|
|
|
mapfile -t targets < <(printf '%s\n' "${purge_selected[@]}" | sort -n)
|
|
|
|
|
for i in "${targets[@]}"; do
|
|
|
|
|
name=${R_NAME[i]}
|
|
|
|
|
if [[ ${R_OLD[i]} == - ]]; then
|
|
|
|
|
say "$name is not installed (the upgrade would install it as a new dependency): nothing to purge; use $((R_NUM[i]))- to exclude it from this run."
|
|
|
|
|
return 0
|
|
|
|
|
rejected[$name]=1
|
|
|
|
|
continue
|
|
|
|
|
fi
|
|
|
|
|
rd=$(be rdepends "$name") || rd=''
|
|
|
|
|
rd=$(printf '%s\n' "$rd" | sed '/^$/d' | tr '\n' ' ')
|
|
|
|
|
rd=${rd% }
|
|
|
|
|
if [[ -n $rd ]]; then
|
|
|
|
|
reverse[$name]=$(printf '%s\n' "$rd" | sed '/^$/d' | tr '\n' ' ')
|
|
|
|
|
done
|
|
|
|
|
# A refused dependent also blocks its dependencies, including through a chain.
|
|
|
|
|
changed=1
|
|
|
|
|
while ((changed)); do
|
|
|
|
|
changed=0
|
|
|
|
|
for i in "${targets[@]}"; do
|
|
|
|
|
name=${R_NAME[i]}
|
|
|
|
|
[[ -z ${rejected[$name]+x} ]] || continue
|
|
|
|
|
read -r -a rdeps <<<"${reverse[$name]}"
|
|
|
|
|
for dep in "${rdeps[@]}"; do
|
|
|
|
|
if [[ -z ${purge_selected[$dep]+x} || -n ${rejected[$dep]+x} ]]; then
|
|
|
|
|
rd=${reverse[$name]% }
|
|
|
|
|
say "not purged: $name is required by: ${rd// /, } - purge the dependents first"
|
|
|
|
|
return 0
|
|
|
|
|
rejected[$name]=1
|
|
|
|
|
changed=1
|
|
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
out=$(be purge_plan "$name" | sort -u) || rc=$?
|
|
|
|
|
done
|
|
|
|
|
done
|
|
|
|
|
done
|
|
|
|
|
for i in "${targets[@]}"; do
|
|
|
|
|
name=${R_NAME[i]}
|
|
|
|
|
[[ -z ${rejected[$name]+x} ]] || continue
|
|
|
|
|
target_names+=("$name")
|
|
|
|
|
allowed[$name]=1
|
|
|
|
|
closure_of "$name"
|
|
|
|
|
for dep in ${CLOSURE[@]+"${CLOSURE[@]}"}; do allowed[$dep]=1; done
|
|
|
|
|
done
|
|
|
|
|
((${#target_names[@]})) || return 0
|
|
|
|
|
out=$(be purge_plan "${target_names[@]}" | sort -u) || rc=$?
|
|
|
|
|
if ((rc)); then
|
|
|
|
|
err "cannot purge $name: $(be label) refuses (see above); nothing was removed"
|
|
|
|
|
err "cannot purge ${target_names[*]}: $(be label) refuses (see above); nothing was removed"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
while IFS= read -r p; do
|
|
|
|
|
[[ -n $p && -z ${gone[$p]+x} ]] || continue
|
|
|
|
|
[[ -n $p ]] || continue
|
|
|
|
|
total_plan+=("$p")
|
|
|
|
|
gone[$p]=1
|
|
|
|
|
plan+=("$p")
|
|
|
|
|
[[ $p == "$name" ]] || others+=("$p")
|
|
|
|
|
[[ -n ${purge_selected[$p]+x} && -z ${rejected[$p]+x} ]] || total_others+=("$p")
|
|
|
|
|
[[ -n ${allowed[$p]+x} && -z ${rejected[$p]+x} ]] || extra+=("$p")
|
|
|
|
|
done <<<"$out"
|
|
|
|
|
if ((${#plan[@]} == 0)); then
|
|
|
|
|
say "$name is not installed: nothing to purge."
|
|
|
|
|
if ((${#total_plan[@]} == 0)); then
|
|
|
|
|
say "${target_names[*]} is not installed: nothing to purge."
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
# belt and braces: the plan may only hold the target and its own (installed) dependencies;
|
|
|
|
|
# anything else means a dependent the reverse-dependency query missed (e.g. through a virtual)
|
|
|
|
|
if ((${#others[@]})); then
|
|
|
|
|
closure_of "$name"
|
|
|
|
|
for d in ${CLOSURE[@]+"${CLOSURE[@]}"}; do mine[$d]=1; done
|
|
|
|
|
for p in "${others[@]}"; do [[ -n ${mine[$p]+x} ]] || extra+=("$p"); done
|
|
|
|
|
for name in "${target_names[@]}"; do
|
|
|
|
|
if [[ -z ${gone[$name]+x} ]]; then
|
|
|
|
|
err "cannot purge $name: $(be label) did not include it in the plan; nothing was removed"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
# belt and braces: no package outside the targets and their dependencies may be removed.
|
|
|
|
|
if ((${#extra[@]})); then
|
|
|
|
|
err "cannot purge $name: $(be label) would also remove $(join_by ', ' "${extra[@]}"), which $name does not depend on (dependents of it?); nothing was removed"
|
|
|
|
|
err "cannot purge ${target_names[*]}: $(be label) would also remove $(join_by ', ' "${extra[@]}"), which ${target_names[*]} does not depend on (dependents of it?); nothing was removed"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
prompt="Purge $name now?"
|
|
|
|
|
((${#others[@]} == 0)) || prompt+=" This also removes: $(join_by ', ' "${others[@]}")"
|
|
|
|
|
prompt="Purge ${target_names[*]} now?"
|
|
|
|
|
((${#total_others[@]} == 0)) || prompt+=" This also removes: $(join_by ', ' "${total_others[@]}")"
|
|
|
|
|
read_line "$prompt [y/N] "
|
|
|
|
|
case $REPLY in
|
|
|
|
|
y | Y | yes | YES | Yes) ;;
|
|
|
|
|
*)
|
|
|
|
|
say "not purged: $name"
|
|
|
|
|
say "not purged: ${target_names[*]}"
|
|
|
|
|
return 0
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
be purge "${plan[@]}" || rc=$?
|
|
|
|
|
rc=0
|
|
|
|
|
be purge "${total_plan[@]}" || rc=$?
|
|
|
|
|
if ((rc)); then
|
|
|
|
|
err "purge of $name failed ($(be label) exited $rc); the row stays in the list"
|
|
|
|
|
if ((${#target_names[@]} == 1)); then
|
|
|
|
|
err "purge of ${target_names[0]} failed ($(be label) exited $rc); the row stays in the list"
|
|
|
|
|
else
|
|
|
|
|
err "purge of ${target_names[*]} failed ($(be label) exited $rc); the rows stay in the list"
|
|
|
|
|
fi
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
say "purged: ${plan[*]}"
|
|
|
|
|
say "purged: ${total_plan[*]}"
|
|
|
|
|
for ((i = 0; i < ${#R_NAME[@]}; i++)); do
|
|
|
|
|
if [[ -n ${gone[${R_NAME[i]}]+x} ]]; then
|
|
|
|
|
R_GONE[i]=1
|
|
|
|
@@ -2768,9 +2808,21 @@ 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_flush_purges() {
|
|
|
|
|
local -n pb=$1
|
|
|
|
|
if ((${#pb[@]})); then
|
|
|
|
|
session_purge "${pb[@]}"
|
|
|
|
|
pb=()
|
|
|
|
|
if (($(rows_left) == 0 && ${#R_NOTES[@]} == 0)); then
|
|
|
|
|
say "Nothing left to upgrade."
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
session_line() {
|
|
|
|
|
local tok n op i name
|
|
|
|
|
local -a toks=() batch=()
|
|
|
|
|
local -a toks=() batch=() purge_batch=()
|
|
|
|
|
local -A initial=() named=()
|
|
|
|
|
local re='^([0-9]{1,6})(\?\?|\?|-|x|\+)$'
|
|
|
|
|
local name_re='^([A-Za-z0-9_+@][A-Za-z0-9_+.@:~-]*)(\?\?|\?)$'
|
|
|
|
@@ -2797,6 +2849,24 @@ session_line() {
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
for tok in "${toks[@]}"; do
|
|
|
|
|
if [[ $tok =~ $re ]]; then
|
|
|
|
|
op=${BASH_REMATCH[2]}
|
|
|
|
|
if [[ $op == x ]]; then
|
|
|
|
|
if ((${#batch[@]})); then session_trust "${batch[@]}"; batch=(); fi
|
|
|
|
|
elif [[ $op == + ]]; then
|
|
|
|
|
session_flush_purges purge_batch
|
|
|
|
|
else
|
|
|
|
|
if ((${#batch[@]})); then session_trust "${batch[@]}"; batch=(); fi
|
|
|
|
|
session_flush_purges purge_batch
|
|
|
|
|
fi
|
|
|
|
|
elif [[ $tok =~ $name_re ]]; then
|
|
|
|
|
if ((${#batch[@]})); then session_trust "${batch[@]}"; batch=(); fi
|
|
|
|
|
session_flush_purges purge_batch
|
|
|
|
|
else
|
|
|
|
|
if ((${#batch[@]})); then session_trust "${batch[@]}"; batch=(); fi
|
|
|
|
|
session_flush_purges purge_batch
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ $tok =~ $re ]]; then
|
|
|
|
|
n=$((10#${BASH_REMATCH[1]}))
|
|
|
|
|
op=${BASH_REMATCH[2]}
|
|
|
|
@@ -2809,7 +2879,10 @@ session_line() {
|
|
|
|
|
batch+=("$i")
|
|
|
|
|
continue
|
|
|
|
|
fi
|
|
|
|
|
if ((${#batch[@]})); then session_trust "${batch[@]}"; batch=(); fi
|
|
|
|
|
if [[ $op == x ]]; then
|
|
|
|
|
purge_batch+=("$i")
|
|
|
|
|
continue
|
|
|
|
|
fi
|
|
|
|
|
case $op in
|
|
|
|
|
'?') print_info "${R_NAME[i]}" "${R_OLD[i]}" || true ;;
|
|
|
|
|
'??') print_full_info "${R_NAME[i]}" "${R_OLD[i]}" || true ;;
|
|
|
|
@@ -2822,13 +2895,6 @@ session_line() {
|
|
|
|
|
say "excluded from this run: ${R_NAME[i]}"
|
|
|
|
|
fi
|
|
|
|
|
;;
|
|
|
|
|
x)
|
|
|
|
|
session_purge "$i"
|
|
|
|
|
if (($(rows_left) == 0 && ${#R_NOTES[@]} == 0)); then
|
|
|
|
|
say "Nothing left to upgrade."
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
elif [[ $tok =~ $name_re ]]; then
|
|
|
|
|
name=${BASH_REMATCH[1]}
|
|
|
|
@@ -2838,14 +2904,12 @@ session_line() {
|
|
|
|
|
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=()
|
|
|
|
@@ -2865,6 +2929,7 @@ session_line() {
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
if ((${#batch[@]})); then session_trust "${batch[@]}"; fi
|
|
|
|
|
session_flush_purges purge_batch
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd_interactive() {
|
|
|
|
|