diff --git a/syncthing-resolve-conflicts.sh b/syncthing-resolve-conflicts.sh index efbdf48..662e7be 100755 --- a/syncthing-resolve-conflicts.sh +++ b/syncthing-resolve-conflicts.sh @@ -1,4 +1,4 @@ -#!/usr/bin/bash +#!/usr/bin/env bash # Check for sync conflicts and resolve them. Idea from the shell # script 'pacdiff'. @@ -12,41 +12,60 @@ declare -a ignored declare -a nontext plain() { - (( QUIET )) && return - local mesg=$1; shift - printf " ${mesg}${ALL_OFF}\n" "$@" >&1 + (( QUIET )) && return + local mesg + 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 + (( QUIET )) && return + local mesg + mesg="$1"; shift + # shellcheck disable=SC2059 + 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 + (( QUIET )) && return + local mesg + mesg="$1"; shift + # shellcheck disable=SC2059 + printf "${BLUE} ->${ALL_OFF} ${mesg}${ALL_OFF}\n" "$@" >&1 } ask() { - local mesg=$1; shift - printf " ${BLUE}::${ALL_OFF} ${mesg}${ALL_OFF}" "$@" >&1 + local mesg + mesg="$1"; shift + # shellcheck disable=SC2059 + printf " ${BLUE}::${ALL_OFF} ${mesg}${ALL_OFF}" "$@" >&1 } +if [ -n "$(command -v gettext)" ]; then + translate() { + gettext "$@" + } +else + translate() { + printf %s "$@" + } +fi warning() { - local mesg=$1; shift - echo - printf "${YELLOW}==> $(gettext "WARNING:")${ALL_OFF} ${mesg}${ALL_OFF}\n" "$@" >&2 + local mesg + mesg="$1"; shift + # shellcheck disable=SC2059 + printf "\n${YELLOW}==> $(translate "WARNING:")${ALL_OFF} ${mesg}${ALL_OFF}\n" "$@" >&2 } error() { - local mesg=$1; shift - printf "${RED}==> $(gettext "ERROR:")${ALL_OFF} ${mesg}${ALL_OFF}\n" "$@" >&2 + local mesg + mesg="$1"; shift + # shellcheck disable=SC2059 + printf "${RED}==> $(translate "ERROR:")${ALL_OFF} ${mesg}${ALL_OFF}\n" "$@" >&2 } usage() { - cat <' - echo 'Inspired by "pacdiff".' + echo 'Inspired by "pacdiff".' } cmd() { - locate -0 -e -b sync-conflict + case "$(uname -s)" in + Darwin) locate -0 sync-conflict;; + *) locate -0 -e -b sync-conflict ;; + esac } 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 + 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 + # 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 +if [ -z "$(command -v "${diffprog%% *}")" ] && (( ! 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 + [[ ! -e "$conflict" && ! -L "$conflict" && ! -d "$conflict" ]] && continue + if (( OUTPUTONLY )); then + echo "$conflict" + continue + fi # Ignore backups in '.stversions' folders. if [[ "$conflict" = */.stversions/* ]] then - ignored+=("$conflict") - continue + ignored+=("$conflict") + continue fi # XXX: Maybe somebody wants to diff special non-text files? - + # Ignore binary files. - if [[ -z $(file -i "$conflict" | grep text) ]] + if file -i "$conflict" | grep -qv text then - nontext+=("$conflict") - continue + nontext+=("$conflict") + continue fi - + # XXX: Recursive sync conflicts lead to problems if they are # treated in the wrong order. @@ -152,8 +175,7 @@ while IFS= read -u 3 -r -d '' conflict; do # Original filename. file="${conflict/.sync-conflict-????????-??????/}" - - msg "Sync conflict found: %s" "$conflict" + msg "Sync conflict: %s" "$conflict" # Handle sync conflict if original file exists. if [ ! -f "$file" ] then @@ -163,25 +185,29 @@ while IFS= read -u 3 -r -d '' conflict; do 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 + if test "$conflict" -ef "$file"; then + warning "Original file and conflict file point to the same file. Ignoring conflict." + continue + elif 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] " + # shellcheck disable=SC2162 + 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 @@ -190,10 +216,27 @@ if [ ! ${#ignored[@]} -eq 0 ] then warning "Some files have been ignored." (( ${#ignored[@]} )) && msg "%s" "${ignored[@]}" - ask "(R)emove ignored files, (Q)uit: [r/q] " + ask "(R)emove all ignored files, (A)sk for each file, (Q)uit: [r/q] " + # shellcheck disable=SC2162 while read c; do - case $c in + case "$c" in q|Q) exit 0 ;; + a|A) + for f in "${ignored[@]}" + do + msg "Ignored file: %s" "$f" + ask "(R)emove, (S)kip, (Q)uit: [r/s/q] " + # shellcheck disable=SC2162 + while read ci; do + case "$ci" 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 + ;; r|R) (( ${#ignored[@]} )) && plain "$(rm -v "${ignored[@]}")"; break ;; *) ask "Invalid answer. Try again: [d/q] "; continue ;; esac @@ -204,13 +247,14 @@ fi # delete them one by one if specified. if [ ! ${#nontext[@]} -eq 0 ] then - warning "Some files that are not text have been ignored." + warning "The following files that are non-text:" for f in "${nontext[@]}" do - msg "%s" "$f" + msg "Non-text file: %s" "$f" ask "(R)emove, (S)kip, (Q)uit: [r/s/q] " + # shellcheck disable=SC2162 while read c; do - case $c in + case "$c" in q|Q) exit 0 ;; s|S) break ;; r|R) plain "$(rm -v "$f")"; break ;;