diff --git a/syncthing-resolve-conflicts.sh b/syncthing-resolve-conflicts.sh new file mode 100755 index 0000000..efbdf48 --- /dev/null +++ b/syncthing-resolve-conflicts.sh @@ -0,0 +1,223 @@ +#!/usr/bin/bash + +# Check for sync conflicts and resolve them. Idea from the shell +# script 'pacdiff'. + +declare -r myname='syncthing-resolve-conflicts.sh' +declare -r myver='1.0.0' + +diffprog=${DIFFPROG:-'vim -d'} +USE_COLOR='y' +declare -a ignored +declare -a nontext + +plain() { + (( QUIET )) && return + local mesg=$1; shift + printf " ${mesg}${ALL_OFF}\n" "$@" >&1 +} + +msg() { + (( QUIET )) && return + local mesg=$1; shift + printf "${GREEN}==>${ALL_OFF} ${mesg}${ALL_OFF}\n" "$@" >&1 +} + +msg2() { + (( QUIET )) && return + local mesg=$1; shift + printf "${BLUE} ->${ALL_OFF} ${mesg}${ALL_OFF}\n" "$@" >&1 +} + +ask() { + local mesg=$1; shift + printf " ${BLUE}::${ALL_OFF} ${mesg}${ALL_OFF}" "$@" >&1 +} + +warning() { + local mesg=$1; shift + echo + printf "${YELLOW}==> $(gettext "WARNING:")${ALL_OFF} ${mesg}${ALL_OFF}\n" "$@" >&2 +} + +error() { + local mesg=$1; shift + printf "${RED}==> $(gettext "ERROR:")${ALL_OFF} ${mesg}${ALL_OFF}\n" "$@" >&2 +} + +usage() { + cat <' + echo 'Inspired by "pacdiff".' +} + +cmd() { + locate -0 -e -b sync-conflict +} + +while [[ -n "$1" ]]; do + case "$1" in + -o|--output) + OUTPUTONLY=1;; + --nocolor) + USE_COLOR='n';; + -v|-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. +unset ALL_OFF BOLD BLUE GREEN RED YELLOW +if [[ -t 2 && $USE_COLOR = "y" ]]; then + # Prefer terminal safe colored and bold text when tput is supported. + if tput setaf 0 &>/dev/null; then + ALL_OFF="$(tput sgr0)" + BLUE="$(tput setaf 27)" + GREEN="$(tput setaf 2)" + RED="$(tput setaf 1)" + YELLOW="$(tput setaf 3)" + else + ALL_OFF="\e[1;0m" + BLUE="\e[1;34m" + GREEN="\e[1;32m" + RED="\e[1;31m" + YELLOW="\e[1;33m" + fi +fi +readonly ALL_OFF BOLD BLUE GREEN RED YELLOW + +if ! type -p ${diffprog%% *} >/dev/null && (( ! OUTPUTONLY )); then + error "Cannot find the $diffprog binary required for viewing differences." + exit 1 +fi + +warning "Recursive sync conflicts are not properly handled." + +# See http://mywiki.wooledge.org/BashFAQ/020. +while IFS= read -u 3 -r -d '' conflict; do + if (( OUTPUTONLY )); then + echo "$conflict" + continue + fi + + # Ignore backups in '.stversions' folders. + if [[ "$conflict" = */.stversions/* ]] + then + ignored+=("$conflict") + continue + fi + + # XXX: Maybe somebody wants to diff special non-text files? + + # Ignore binary files. + if [[ -z $(file -i "$conflict" | grep text) ]] + then + nontext+=("$conflict") + continue + fi + + # XXX: Recursive sync conflicts lead to problems if they are + # treated in the wrong order. + + # TODO: Improve pattern match (digits only). + + # Original filename. + file="${conflict/.sync-conflict-????????-??????/}" + + msg "Sync conflict found: %s" "$conflict" + # Handle sync conflict if original file exists. + if [ ! -f "$file" ] + then + warning "Original file does not exist for conflict %s." "$conflict" + ignored+=("$conflict") + continue + fi + msg2 "Original file: %s" "$file" + + if cmp -s "$conflict" "$file"; then + msg2 "Files are identical, removing..." + plain "$(rm -v "$conflict")" + else + ask "(V)iew, (S)kip, (R)emove sync conflict, (O)verwrite with sync conflict, (Q)uit: [v/s/r/o/q] " + while read c; do + case $c in + q|Q) exit 0;; + r|R) plain "$(rm -v "$conflict")"; break ;; + o|O) plain "$(mv -v "$conflict" "$file")"; break ;; + v|V) + $diffprog "$conflict" "$file" + ask "(V)iew, (S)kip, (R)emove sync conflict, (O)verwrite with sync conflict, (Q)uit: [v/s/r/o/q] " + continue ;; + s|S) break ;; + *) ask "Invalid answer. Try again: [v/s/r/o/q] "; continue ;; + esac + done + fi +done 3< <(cmd) + +# Print warning if files have been ignored and delete them if +# specified. +if [ ! ${#ignored[@]} -eq 0 ] +then + warning "Some files have been ignored." + (( ${#ignored[@]} )) && msg "%s" "${ignored[@]}" + ask "(R)emove ignored files, (Q)uit: [r/q] " + while read c; do + case $c in + q|Q) exit 0 ;; + r|R) (( ${#ignored[@]} )) && plain "$(rm -v "${ignored[@]}")"; break ;; + *) ask "Invalid answer. Try again: [d/q] "; continue ;; + esac + done +fi + +# Print warning if non-text sync conflicts have been detected and +# delete them one by one if specified. +if [ ! ${#nontext[@]} -eq 0 ] +then + warning "Some files that are not text have been ignored." + for f in "${nontext[@]}" + do + msg "%s" "$f" + ask "(R)emove, (S)kip, (Q)uit: [r/s/q] " + while read c; do + case $c in + q|Q) exit 0 ;; + s|S) break ;; + r|R) plain "$(rm -v "$f")"; break ;; + *) ask "Invalid answer. Try again: [r/s/q] "; continue ;; + esac + done + done +fi + +exit 0