Compare commits

..
1 Commits
Author SHA1 Message Date
Felitendo 9fd2458d20 Hold back blocking packages instead of failing the whole upgrade
A repo package that replaces something an installed AUR package still depends
on aborted the entire transaction, and would have done so on every subsequent
run - one stale AUR package was enough to cut a machine off from all updates
indefinitely. Observed in the wild: percona-server-clients replaces
libperconaserverclient without providing it, while heidisql-qt6-bin hard-depends
on it, blocking 213 unrelated package updates.

pacman names the offending package in its dependency errors, so it is now
extracted and passed to --ignore for one retry: the other 213 packages go
through and the blocker is reported. The hold is per-run, never written to
IgnorePkg, so it disappears by itself once upstream catches up.

The single retry is also now a bounded recovery loop, because fixing one
problem regularly uncovers the next - a conflict resolved with --ask=20 can
surface a dependency error behind it. Each remedy is applied at most once.

The held-back set is shown in the menu and notified only when it changes, so a
blocker waiting on an upstream fix does not produce the same message daily.
2026-08-08 15:12:43 +02:00
6 changed files with 112 additions and 19 deletions

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@
# Overridable so a packager can pass the version it is actually building
# (`make VERSION=$pkgver`). The literal below is the fallback for builds
# straight from a checkout, and is what a release tag has to carry.
VERSION ?= 1.0.4
VERSION ?= 1.0.5
PREFIX ?= /usr
DESTDIR ?=
+11
View File
@@ -243,3 +243,14 @@ msgstr ""
msgid "pacman's lock file looks left over from an interrupted update. Updates are paused until it is cleared."
msgstr ""
msgid "Some packages were held back"
msgstr ""
#, c-format
msgid "%s could not be updated and was skipped. Everything else is up to date."
msgstr ""
#, c-format
msgid "Held back: %s"
msgstr ""
+11
View File
@@ -244,3 +244,14 @@ msgstr "Paketdatenbank gesperrt"
msgid "pacman's lock file looks left over from an interrupted update. Updates are paused until it is cleared."
msgstr "Die Sperrdatei von pacman scheint von einem abgebrochenen Update übrig zu sein. Bis sie entfernt ist, pausieren die Updates."
msgid "Some packages were held back"
msgstr "Einige Pakete wurden zurückgehalten"
#, c-format
msgid "%s could not be updated and was skipped. Everything else is up to date."
msgstr "%s konnte nicht aktualisiert werden und wurde übersprungen. Alles andere ist aktuell."
#, c-format
msgid "Held back: %s"
msgstr "Zurückgehalten: %s"
+16
View File
@@ -202,6 +202,22 @@ else
fi
fi
# Packages skipped so the rest of the upgrade could proceed. The notification
# only fires when the set changes: a blocker waiting on an upstream fix would
# otherwise produce the same message every single day.
prev_held="$(cau_state_read held_back '')"
if [[ -n $CAU_PACMAN_HELD ]]; then
cau_state_write held_back "$CAU_PACMAN_HELD"
cau_warn "Held back: $CAU_PACMAN_HELD"
if [[ $CAU_PACMAN_HELD != "$prev_held" && $CFG_NOTIFY_ERROR == yes ]]; then
cau_notify normal "Some packages were held back" \
"%s could not be updated and was skipped. Everything else is up to date." \
"$CAU_PACMAN_HELD"
fi
else
cau_state_clear held_back
fi
if cau_pacman_reboot_needed; then
cau_state_write reboot_needed 1
cau_info "A kernel update needs a restart"
+7
View File
@@ -87,6 +87,13 @@ cau_ui_status() {
"$(cau_msg "A restart is recommended to finish a kernel update.")" \
"$CAU_C_RESET"
fi
local held
held="$(cau_state_read held_back '')"
if [[ -n $held ]]; then
printf '\n %s%s%s\n' "$CAU_C_YELLOW" \
"$(cau_msg "Held back: %s" "$held")" "$CAU_C_RESET"
fi
}
# cau_ui_status_conditions
+66 -18
View File
@@ -10,6 +10,9 @@
CAU_PACMAN_COUNT=0
CAU_PACMAN_PENDING=''
# Packages that had to be skipped so the rest of the upgrade could go through.
CAU_PACMAN_HELD=''
# Base flags for every unattended pacman invocation.
cau_pacman_flags() {
printf '%s\n' --noconfirm --color never --noprogressbar --disable-download-timeout
@@ -55,6 +58,8 @@ _cau_pacman_classify() {
if grep -qiE 'are in conflict|unresolvable package conflicts' "$log"; then
printf 'conflict\n'
elif grep -qiE 'could not satisfy dependencies|breaks dependency|unable to satisfy dependency' "$log"; then
printf 'dependency\n'
elif grep -qiE 'signature from .* is (unknown trust|marginal trust|invalid)|invalid or corrupted package \(PGP signature\)|key ".*" is unknown|keyring is not writable' "$log"; then
printf 'keyring\n'
elif grep -qiE 'exists in filesystem' "$log"; then
@@ -64,6 +69,25 @@ _cau_pacman_classify() {
fi
}
# _cau_pacman_blockers <logfile>
# The packages standing in the way of an otherwise fine upgrade. pacman names
# them in its dependency errors:
#
# :: removing libperconaserverclient breaks dependency 'libperconaserverclient'
# required by heidisql-qt6-bin
# :: unable to satisfy dependency 'foo' required by bar
#
# In the first form the package being removed is the one to keep; in the second
# it is the package that cannot be installed.
_cau_pacman_blockers() {
local log="$1"
{
sed -nE "s/.*removing ([^ ]+) breaks dependency.*/\\1/p" "$log"
sed -nE "s/.*unable to satisfy dependency '[^']*' required by ([^ ]+).*/\\1/p" "$log"
} | grep -E '^[A-Za-z0-9@._+-]+$' | sort -u
}
# cau_pacman_update
# Returns 0 on success (including "nothing to do"), 1 on a failure the user
# needs to hear about. CAU_PACMAN_COUNT holds how many packages moved.
@@ -90,8 +114,17 @@ cau_pacman_update() {
mapfile -t flags < <(cau_pacman_flags)
log="$(mktemp)" || return 1
if pacman -Syu "${flags[@]}" > "$log" 2>&1; then
# Recovery loop rather than a single retry: fixing one problem regularly
# uncovers the next (a conflict resolved into a dependency error, say).
# Each remedy is applied at most once, so this always terminates.
local -a extra=() blockers=() tried=()
local attempt=0 b
while true; do
if pacman -Syu "${flags[@]}" "${extra[@]}" > "$log" 2>&1; then
cat "$log" >> "$CAU_RUNLOG" 2>/dev/null
grep -E '^(removing|replacing) ' "$log" 2>/dev/null \
| while read -r line; do cau_info " $line"; done
rm -f "$log"
return 0
fi
@@ -100,6 +133,11 @@ cau_pacman_update() {
kind="$(_cau_pacman_classify "$log")"
cau_warn "pacman -Syu failed ($kind)"
if (( ++attempt > 3 )) || [[ " ${tried[*]} " == *" $kind "* ]]; then
break
fi
tried+=("$kind")
case "$kind" in
keyring)
# A stale keyring is the one failure that is always safe to fix
@@ -111,12 +149,6 @@ cau_pacman_update() {
if (( ${#keyrings[@]} )); then
cau_run_logged pacman -Sy --noconfirm --color never "${keyrings[@]}" || true
fi
if pacman -Syu "${flags[@]}" > "$log" 2>&1; then
cat "$log" >> "$CAU_RUNLOG" 2>/dev/null
rm -f "$log"
return 0
fi
cat "$log" >> "$CAU_RUNLOG" 2>/dev/null
;;
conflict)
@@ -126,18 +158,29 @@ cau_pacman_update() {
# question bitmask: 4 = CONFLICT_PKG, 16 = REMOVE_PKGS.
if [[ $CFG_RESOLVE_CONFLICTS != yes ]]; then
cau_error "Package conflict requires a decision (AutoResolveConflicts is off)"
rm -f "$log"
return 1
break
fi
cau_info "Resolving package conflicts automatically and retrying"
if pacman -Syu "${flags[@]}" --ask=20 > "$log" 2>&1; then
cat "$log" >> "$CAU_RUNLOG" 2>/dev/null
grep -E '^(removing|replacing) ' "$log" 2>/dev/null \
| while read -r line; do cau_info " $line"; done
rm -f "$log"
return 0
extra+=(--ask=20)
;;
dependency)
# Something installed still depends on a package the repos want
# to drop or replace - almost always an AUR package that has not
# caught up yet. Nothing here can fix that, and it is not worth
# failing over: letting one stuck package block every other
# update indefinitely is far worse on an unattended machine.
# Hold the blockers back and upgrade everything else.
mapfile -t blockers < <(_cau_pacman_blockers "$log")
if (( ${#blockers[@]} == 0 )); then
cau_error "Dependency problem with no package to hold back"
break
fi
cat "$log" >> "$CAU_RUNLOG" 2>/dev/null
for b in "${blockers[@]}"; do
extra+=(--ignore "$b")
done
CAU_PACMAN_HELD="${blockers[*]}"
cau_warn "Holding back ${blockers[*]} and retrying without them"
;;
filesystem)
@@ -145,13 +188,18 @@ cau_pacman_update() {
# silently clobber something the user put there deliberately, so
# this one stays a human decision.
cau_error "Files on disk conflict with the update; manual review needed"
rm -f "$log"
return 1
break
;;
*)
break
;;
esac
done
rm -f "$log"
CAU_PACMAN_COUNT=0
CAU_PACMAN_HELD=''
return 1
}