2023-09-22 00:10:50 +02:00

358 lines
8.5 KiB
Bash

#!/usr/bin/bash
# pacdiff : a simple pacnew/pacsave updater
#
# Copyright (c) 2007 Aaron Griffin <aaronmgriffin@gmail.com>
# Copyright (c) 2013-2016 Pacman Development Team <pacman-dev@archlinux.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
shopt -s extglob
declare -r myname='pacdiff'
declare -r myver='1.9.1'
LIBRARY=${LIBRARY:-'/usr/share/makepkg'}
diffprog=${DIFFPROG:-'vim -d'}
diffsearchpath=${DIFFSEARCHPATH:-/etc}
mergeprog=${MERGEPROG:-'diff3 -m'}
cachedirs=()
USE_COLOR='y'
SUDO=''
declare -a oldsaves
declare -i USE_FIND=0 USE_LOCATE=0 USE_PACDB=0 OUTPUTONLY=0 BACKUP=0 THREE_WAY_DIFF=0
# Import libmakepkg
source "$LIBRARY"/util/message.sh
die() {
error "$@"
exit 1
}
usage() {
cat <<EOF
${myname} v${myver}
pacorig, pacnew and pacsave maintenance utility.
Usage: $myname [options]
Search Options: select one (default: --pacmandb)
-f, --find scan using find
-l, --locate scan using locate
-p, --pacmandb scan active config files from pacman database
General Options:
-b, --backup when overwriting, save old files with .bak
-c, --cachedir <dir> scan "dir" for 3-way merge base candidates
(default: read from /etc/pacman.conf)
--nocolor do not colorize output
-o, --output print files instead of merging them
-s, --sudo use sudo and sudoedit to merge/remove files
-3, --threeway view diffs in 3-way fashion
-h, --help show this help message and exit
-V, --version display version information and exit
Environment Variables:
DIFFPROG override the merge program (default: 'vim -d')
DIFFSEARCHPATH override the search path (only when using find)
(default: /etc)
MERGEPROG override the 3-way merge program (default: 'diff3 -m')
Example: DIFFPROG=meld DIFFSEARCHPATH="/boot /etc /usr" MERGEPROG="git merge-file -p" $myname
Example: $myname --output --locate
EOF
}
version() {
printf "%s %s\n" "$myname" "$myver"
echo 'Copyright (C) 2007 Aaron Griffin <aaronmgriffin@gmail.com>'
echo 'Copyright (C) 2013-2016 Pacman Development Team <pacman-dev@archlinux.org>'
}
print_existing() {
[[ -f "$1" ]] && printf '%s\0' "$1"
}
print_existing_pacsave(){
for f in "${1}"?(.+([0-9])); do
[[ -f $f ]] && printf '%s\0' "$f"
done
}
base_cache_tar() {
package="$1"
for cachedir in "${cachedirs[@]}"; do
pushd "$cachedir" &>/dev/null || {
error "failed to chdir to '%s', skipping" "$cachedir"
continue
}
find "$PWD" -name "$package-[0-9]*.pkg.tar*" ! -name '*.sig' |
pacsort --files --reverse | sed -ne '2p'
popd &>/dev/null
done
}
diffprog_fn() {
if [[ -n "$SUDO" ]]; then
SUDO_EDITOR="$diffprog" sudoedit "$@"
else
$diffprog "$@"
fi
}
view_diff() {
pacfile="$1"
file="$2"
package="$(pacman -Qoq "$file")" || return 1
base_tar="$(base_cache_tar "$package")"
two_way_diff() {
diffprog_fn "$pacfile" "$file"
}
three_way_diff() {
diffprog_fn "$pacfile" "$base" "$file"
}
unset tempdir
if (( ! THREE_WAY_DIFF )); then
two_way_diff
elif [[ -z $base_tar ]]; then
msg2 "Unable to find a base package. falling back to 2-way diff."
two_way_diff
else
basename="$(basename "$file")"
tempdir="$(mktemp -d --tmpdir "pacdiff-diff-$basename.XXX")"
base="$(mktemp "$tempdir"/"$basename.base.XXX")"
merged="$(mktemp "$tempdir"/"$basename.merged.XXX")"
if ! bsdtar -xqOf "$base_tar" "${file#/}" >"$base"; then
msg2 "Unable to extract the previous version of this file. falling back to 2-way diff."
two_way_diff
else
three_way_diff
fi
fi
ret=1
if cmp -s "$pacfile" "$file"; then
msg2 "Files are identical, removing..."
$SUDO rm -v "$pacfile"
ret=0
fi
$SUDO rm -rf "$tempdir"
return $ret
}
merge_file() {
pacfile="$1"
file="$2"
package="$(pacman -Qoq "$file")" || return 1
base_tar="$(base_cache_tar "$package")"
if [[ -z $base_tar ]]; then
msg2 "Unable to find a base package."
return 1
fi
basename="$(basename "$file")"
tempdir="$(mktemp -d --tmpdir "pacdiff-merge-$basename.XXX")"
base="$(mktemp "$tempdir"/"$basename.base.XXX")"
merged="$(mktemp "$tempdir"/"$basename.merged.XXX")"
if ! /usr/bin/bsdtar -xqOf "$base_tar" "${file#/}" >"$base"; then
msg2 "Unable to extract the previous version of this file."
return 1
fi
if $mergeprog "$file" "$base" "$pacfile" >"$merged"; then
msg2 "Merged without conflicts."
fi
$diffprog "$file" "$merged"
while :; do
ask "Would you like to use the results of the merge? [y/n] "
read c || return 1
case $c in
y|Y) break ;;
n|N) return 1 ;;
*) msg2 "Invalid answer." ;;
esac
done
if ! $SUDO cp -v "$merged" "$file"; then
warning "Unable to write merged file to %s. Merged file is preserved at %s" "$file" "$merged"
return 1
fi
$SUDO rm -rv "$pacfile" "$tempdir"
return 0
}
cmd() {
if (( USE_LOCATE )); then
locate -0 -e -b \*.pacnew \*.pacorig \*.pacsave '*.pacsave.[0-9]*'
elif (( USE_FIND )); then
find $diffsearchpath \( -name \*.pacnew -o -name \*.pacorig -o -name \*.pacsave -o -name '*.pacsave.[0-9]*' \) -print0
elif (( USE_PACDB )); then
awk '/^%BACKUP%$/ {
while (getline) {
if (/^$/) { nextfile }
print $1
}
}' "${pac_db}"/*/files | while read -r bkup; do
print_existing "/$bkup.pacnew"
print_existing "/$bkup.pacorig"
print_existing_pacsave "/$bkup.pacsave"
done
fi
}
while [[ -n "$1" ]]; do
case "$1" in
-f|--find)
USE_FIND=1;;
-l|--locate)
USE_LOCATE=1;;
-p|--pacmandb)
USE_PACDB=1;;
-b|--backup)
BACKUP=1;;
-c|--cachedir)
cachedirs+=("$2"); shift;;
--nocolor)
USE_COLOR='n';;
-o|--output)
OUTPUTONLY=1;;
-s|--sudo)
SUDO=sudo;;
-3|--threeway)
THREE_WAY_DIFF=1 ;;
-V|--version)
version; exit 0;;
-h|--help)
usage; exit 0;;
*)
usage; exit 1;;
esac
shift
done
# check if messages are to be printed using color
if [[ -t 2 && $USE_COLOR != "n" ]]; then
colorize
else
unset ALL_OFF BOLD BLUE GREEN RED YELLOW
fi
if ! type -p ${diffprog%% *} >/dev/null && (( ! OUTPUTONLY )); then
die "Cannot find the $diffprog binary required for viewing differences."
fi
if ! type -p ${mergeprog%% *} >/dev/null && (( ! OUTPUTONLY )); then
die "Cannot find the $mergeprog binary required for merging differences."
fi
case $(( USE_FIND + USE_LOCATE + USE_PACDB )) in
0) USE_PACDB=1;; # set the default search option
[^1]) error "Only one search option may be used at a time"
usage; exit 1;;
esac
if (( USE_PACDB )); then
if ! DBPath="$(pacman-conf DBPath)"; then
error "unable to read /etc/pacman.conf"
usage; exit 1
fi
pac_db="${DBPath:-/var/lib/pacman}/local"
if [[ ! -d "${pac_db}" ]]; then
error "unable to read pacman database %s". "${pac_db}"
usage; exit 1
fi
fi
if [[ -z ${cachedirs[*]} ]]; then
readarray -t cachedirs < <(pacman-conf CacheDir)
fi
# see http://mywiki.wooledge.org/BashFAQ/020
while IFS= read -u 3 -r -d '' pacfile; do
file="${pacfile%.pac*}"
file_type="pac${pacfile##*.pac}"
if (( OUTPUTONLY )); then
echo "$pacfile"
continue
fi
# add matches for pacsave.N to oldsaves array, do not prompt
if [[ $file_type = pacsave.+([0-9]) ]]; then
oldsaves+=("$pacfile")
continue
fi
msg "%s file found for %s" "$file_type" "$file"
if [ ! -f "$file" ]; then
warning "$file does not exist"
$SUDO rm -iv "$pacfile"
continue
fi
if cmp -s "$pacfile" "$file"; then
msg2 "Files are identical, removing..."
$SUDO rm -v "$pacfile"
else
while :; do
ask "(V)iew, (M)erge, (S)kip, (R)emove %s, (O)verwrite with %s, (Q)uit: [v/m/s/r/o/q] " "$file_type" "$file_type"
read c || break
case $c in
q|Q) exit 0;;
r|R) $SUDO rm -v "$pacfile"; break ;;
o|O)
if (( BACKUP )); then
$SUDO cp -v "$file" "$file.bak"
fi
$SUDO mv -v "$pacfile" "$file"
break ;;
v|V)
if view_diff "$pacfile" "$file"; then
break
fi ;;
m|M)
if merge_file "$pacfile" "$file"; then
break
fi ;;
s|S) break ;;
*) msg2 "Invalid answer." ;;
esac
done
fi
done 3< <(cmd)
(( ${#oldsaves[@]} )) && warning "Ignoring %s" "${oldsaves[@]}"
exit 0