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.
This commit is contained in:
1 parent
f593cc1933
commit
103335e7c1
6 files changed
+112
-19
No files matched your search
@@ -8,7 +8,7 @@
|
|||||||
# Overridable so a packager can pass the version it is actually building
|
# Overridable so a packager can pass the version it is actually building
|
||||||
# (`make VERSION=$pkgver`). The literal below is the fallback for builds
|
# (`make VERSION=$pkgver`). The literal below is the fallback for builds
|
||||||
# straight from a checkout, and is what a release tag has to carry.
|
# straight from a checkout, and is what a release tag has to carry.
|
||||||
VERSION ?= 1.0.4
|
VERSION ?= 1.0.5
|
||||||
|
|
||||||
PREFIX ?= /usr
|
PREFIX ?= /usr
|
||||||
DESTDIR ?=
|
DESTDIR ?=
|
||||||
|
|||||||
@@ -243,3 +243,14 @@ msgstr ""
|
|||||||
|
|
||||||
msgid "pacman's lock file looks left over from an interrupted update. Updates are paused until it is cleared."
|
msgid "pacman's lock file looks left over from an interrupted update. Updates are paused until it is cleared."
|
||||||
msgstr ""
|
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 ""
|
||||||
@@ -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."
|
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."
|
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"
|
||||||
@@ -202,6 +202,22 @@ else
|
|||||||
fi
|
fi
|
||||||
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
|
if cau_pacman_reboot_needed; then
|
||||||
cau_state_write reboot_needed 1
|
cau_state_write reboot_needed 1
|
||||||
cau_info "A kernel update needs a restart"
|
cau_info "A kernel update needs a restart"
|
||||||
|
|||||||
@@ -87,6 +87,13 @@ cau_ui_status() {
|
|||||||
"$(cau_msg "A restart is recommended to finish a kernel update.")" \
|
"$(cau_msg "A restart is recommended to finish a kernel update.")" \
|
||||||
"$CAU_C_RESET"
|
"$CAU_C_RESET"
|
||||||
fi
|
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
|
# cau_ui_status_conditions
|
||||||
|
|||||||
+66
-18
@@ -10,6 +10,9 @@
|
|||||||
CAU_PACMAN_COUNT=0
|
CAU_PACMAN_COUNT=0
|
||||||
CAU_PACMAN_PENDING=''
|
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.
|
# Base flags for every unattended pacman invocation.
|
||||||
cau_pacman_flags() {
|
cau_pacman_flags() {
|
||||||
printf '%s\n' --noconfirm --color never --noprogressbar --disable-download-timeout
|
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
|
if grep -qiE 'are in conflict|unresolvable package conflicts' "$log"; then
|
||||||
printf 'conflict\n'
|
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
|
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'
|
printf 'keyring\n'
|
||||||
elif grep -qiE 'exists in filesystem' "$log"; then
|
elif grep -qiE 'exists in filesystem' "$log"; then
|
||||||
@@ -64,6 +69,25 @@ _cau_pacman_classify() {
|
|||||||
fi
|
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
|
# cau_pacman_update
|
||||||
# Returns 0 on success (including "nothing to do"), 1 on a failure the user
|
# 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.
|
# 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)
|
mapfile -t flags < <(cau_pacman_flags)
|
||||||
log="$(mktemp)" || return 1
|
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
|
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"
|
rm -f "$log"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -100,6 +133,11 @@ cau_pacman_update() {
|
|||||||
kind="$(_cau_pacman_classify "$log")"
|
kind="$(_cau_pacman_classify "$log")"
|
||||||
cau_warn "pacman -Syu failed ($kind)"
|
cau_warn "pacman -Syu failed ($kind)"
|
||||||
|
|
||||||
|
if (( ++attempt > 3 )) || [[ " ${tried[*]} " == *" $kind "* ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
tried+=("$kind")
|
||||||
|
|
||||||
case "$kind" in
|
case "$kind" in
|
||||||
keyring)
|
keyring)
|
||||||
# A stale keyring is the one failure that is always safe to fix
|
# A stale keyring is the one failure that is always safe to fix
|
||||||
@@ -111,12 +149,6 @@ cau_pacman_update() {
|
|||||||
if (( ${#keyrings[@]} )); then
|
if (( ${#keyrings[@]} )); then
|
||||||
cau_run_logged pacman -Sy --noconfirm --color never "${keyrings[@]}" || true
|
cau_run_logged pacman -Sy --noconfirm --color never "${keyrings[@]}" || true
|
||||||
fi
|
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)
|
conflict)
|
||||||
@@ -126,18 +158,29 @@ cau_pacman_update() {
|
|||||||
# question bitmask: 4 = CONFLICT_PKG, 16 = REMOVE_PKGS.
|
# question bitmask: 4 = CONFLICT_PKG, 16 = REMOVE_PKGS.
|
||||||
if [[ $CFG_RESOLVE_CONFLICTS != yes ]]; then
|
if [[ $CFG_RESOLVE_CONFLICTS != yes ]]; then
|
||||||
cau_error "Package conflict requires a decision (AutoResolveConflicts is off)"
|
cau_error "Package conflict requires a decision (AutoResolveConflicts is off)"
|
||||||
rm -f "$log"
|
break
|
||||||
return 1
|
|
||||||
fi
|
fi
|
||||||
cau_info "Resolving package conflicts automatically and retrying"
|
cau_info "Resolving package conflicts automatically and retrying"
|
||||||
if pacman -Syu "${flags[@]}" --ask=20 > "$log" 2>&1; then
|
extra+=(--ask=20)
|
||||||
cat "$log" >> "$CAU_RUNLOG" 2>/dev/null
|
;;
|
||||||
grep -E '^(removing|replacing) ' "$log" 2>/dev/null \
|
|
||||||
| while read -r line; do cau_info " $line"; done
|
dependency)
|
||||||
rm -f "$log"
|
# Something installed still depends on a package the repos want
|
||||||
return 0
|
# 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
|
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)
|
filesystem)
|
||||||
@@ -145,13 +188,18 @@ cau_pacman_update() {
|
|||||||
# silently clobber something the user put there deliberately, so
|
# silently clobber something the user put there deliberately, so
|
||||||
# this one stays a human decision.
|
# this one stays a human decision.
|
||||||
cau_error "Files on disk conflict with the update; manual review needed"
|
cau_error "Files on disk conflict with the update; manual review needed"
|
||||||
rm -f "$log"
|
break
|
||||||
return 1
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
break
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
rm -f "$log"
|
rm -f "$log"
|
||||||
CAU_PACMAN_COUNT=0
|
CAU_PACMAN_COUNT=0
|
||||||
|
CAU_PACMAN_HELD=''
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user