#!/usr/bin/env bash

# +---------------------------------------------------------------------------------------------------+
# | Title        : ssh-diff                                                                           |
# | Description  : Diff a file over SSH                                                               |
# | Author       : Sven Wick <sven.wick@gmx.de>                                                       |
# | Contributors : Denis Meiswinkel                                                                   |
# | URL          : https://codeberg.org/vaporup/ssh-tools                                             |
# | Based On     : https://gist.github.com/jhqv/dbd59f5838ae8c83f736bfe951bd80ff                      |
# |                https://serverfault.com/questions/59140/how-do-diff-over-ssh#comment1027981_216496 |
# +---------------------------------------------------------------------------------------------------+

VERSION="ssh-diff (ssh-tools) 1.9"

# shellcheck disable=SC2029

#
#  Some colors for better output
#

     RED='\033[0;91m'
    BOLD='\033[1m'
   RESET='\033[0m'

#
# Usage/Help message
#

function usage() {

cat << EOF

    Usage: ${0##*/} [OPTIONS] FILE [user@]hostname[:FILE]

    There is an extra roundtrip to the remote system
    to check for the existence of the file to be diffed.
    So if you are not using SSH Keys
    you may get prompted twice for a password.

    Use "CHECK_REMOTE_FILE_EXISTS=NO ${0##*/}" to disable that behavior

    DIFF_OPTIONS:

    All options your local diff command supports ( except '-r' ).
    See 'man diff' and 'diff --help' for more information.

    SSH_OPTIONS:

        -4             Use IPv4 only
        -6             Use IPv6 only
        -p port        Port to connect to on the remote host.
                       This can be specified on a per-host basis in the configuration file.

    ENVIRONMENT:

        CHECK_REMOTE_FILE_EXISTS    NO|YES    remote file checking

    EXAMPLES:

        # Default

        ${0##*/} /etc/hosts 192.168.1.10

        ${0##*/} /etc/hosts root@192.168.1.10

        ${0##*/} /etc/hosts root@192.168.1.10:/etc/hosts.old

        # Side-by-Side

        ${0##*/} -y /etc/hosts 192.168.1.10

        ${0##*/} -y /etc/hosts root@192.168.1.10

        ${0##*/} -y /etc/hosts root@192.168.1.10:/etc/hosts.old

        # Unified

        ${0##*/} -u /etc/hosts 192.168.1.10

        ${0##*/} -u /etc/hosts root@192.168.1.10

        ${0##*/} -u /etc/hosts root@192.168.1.10:/etc/hosts.old
EOF

}

if [[ -z $1 || $1 == "--help" ]]; then
    usage
    exit 1
fi

if [[ $1 == "--version" ]]; then
    echo "${VERSION}"
    exit
fi

if ! [[ $# -ge 2 ]]; then
    echo -e "\n  ${RED}Error: Not all filenames given${RESET}" >&2
    usage
    exit 1
fi

function supports_colordiff() {

    type colordiff &> /dev/null

}

function show_header() {

    echo ""
    echo -e "Comparing ${BOLD}${remote_host}:${remote_file}${RESET} (<) with ${BOLD}${local_file}${RESET} (>)"
    echo ""

}

function login_successful_and_remote_file_exists() {

    # shellcheck disable=SC2154
    if [[ ${CHECK_REMOTE_FILE_EXISTS} == "NO" ]]; then
      return 0
    fi

    if [[ -z "${username}" ]]; then
        ssh "${ssh_params[@]}" "${remote_host}" "test -e ${remote_file}" &> /dev/null
    else
        ssh "${ssh_params[@]}" "${username}@${remote_host}" "test -e ${remote_file}" &> /dev/null
    fi

    RETVAL=$?

    [[ ${RETVAL} -eq 255 ]] && { echo -e "\n  ${RED}Error: Could not connect to remote server${RESET}\n" >&2 ; exit "${RETVAL}"; }
    [[ ${RETVAL} -ge   1 ]] && { echo -e "\n  ${RED}Error: Remote file ${remote_file} not found${RESET}\n" >&2 ; exit "${RETVAL}"; }
    [[ ${RETVAL} -eq   0 ]] && true

}

function diff_files() {

    if [[ -z "${username}" ]]; then
        if login_successful_and_remote_file_exists; then
            show_header
            if supports_colordiff; then
                ssh "${ssh_params[@]}" "${remote_host}" "cat ${remote_file}" | diff "${diff_params[@]}" --label "${remote_host}:${remote_file}" - "${local_file}" | colordiff
            else
                # Pipe the output to cat after diffing
                #
                # test.sh fails when colordiff is not installed.
                # Reason is that a normal diff returns with 1
                # when files differ but colordiff changes the return code to 0.

                ssh "${ssh_params[@]}" "${remote_host}" "cat ${remote_file}" | diff "${diff_params[@]}" --label "${remote_host}:${remote_file}" - "${local_file}" | cat
            fi
        fi
    else
        if login_successful_and_remote_file_exists; then
            show_header
            if supports_colordiff; then
                ssh "${ssh_params[@]}" "${username}@${remote_host}" "cat ${remote_file}"  | diff "${diff_params[@]}" --label "${remote_host}:${remote_file}" - "${local_file}" | colordiff
            else
                # Pipe the output to cat after diffing
                #
                # test.sh fails when colordiff is not installed.
                # Reason is that a normal diff returns with 1
                # when files differ but colordiff changes the return code to 0.

                ssh "${ssh_params[@]}" "${username}@${remote_host}" "cat ${remote_file}"  | diff "${diff_params[@]}" --label "${remote_host}:${remote_file}" - "${local_file}" | cat
            fi
        fi
    fi

}

#
# MAIN
#

#
# Get all params from command line
#

params=( "$@" )

#
# Get last 2 params, store them away and remove them so only diff params are left
#

remote_params="${params[ ${#params[@]}-1 ]}"  && unset 'params[ ${#params[@]}-1 ]'
local_filename="${params[ ${#params[@]}-1 ]}" && unset 'params[ ${#params[@]}-1 ]'

diff_params=( "${params[@]}" )

#
# Fish within diff params for ssh options and extract them
#

diff_params_index=0

ssh_params=()
ssh_params_indices=()

for param in "${diff_params[@]}"; do

    next_diff_params_index=$(( diff_params_index +1 ))

    #
    # find -p <port>
    #

    if [[ ${param} == "-p" ]] && [[ -z "${diff_params[${next_diff_params_index}]//[0-9]}" ]]; then
        ssh_params_indices+=( "${diff_params_index}" "${next_diff_params_index}" )
        ssh_params+=( "${param}" "${diff_params[${next_diff_params_index}]}" )
    fi

    #
    # find -4 or -6
    #

    if [[ ${param} == "-4" ]] || [[ ${param} == "-6" ]]; then
        ssh_params_indices+=( "${diff_params_index}" )
        ssh_params+=( "${param}" )
    fi

    diff_params_index=$(( diff_params_index +1 ))

done

# shellcheck disable=SC2034
for ssh_param in "${ssh_params_indices[@]}"; do

    # shellcheck disable=SC2184
    # shellcheck disable=SC2102
    unset diff_params[$ssh_param]

done

#
# Getting username, hostname and filename from command line without using grep and awk
#
# user@host:filename -> user gets stored in $username
#                    -> host gets stored in $remote_host
#                    -> filename gets stored in $remote_file
#

if [[ ${remote_params} == *"@"* ]]; then
    remote_part="${remote_params##*@}"
    username="${remote_params%%@*}"
else
    remote_part=${remote_params}
fi

if [[ ${remote_part} == *":"* ]]; then
    remote_file="${remote_part##*:}"
    remote_host="${remote_part%%:*}"
else
    remote_host=${remote_part}
fi

local_file=$(readlink -f "${local_filename}") # get absolute path to file in case it was relative

if [[ -z "${local_file}" ]]; then
    echo -e "\n  ${RED}Error: Given file not found${RESET}\n" >&2
    exit 1
fi

if [[ ! -f ${local_file} ]]; then
    echo -e "\n  ${RED}Error: Local file ${local_file} not found${RESET}\n" >&2
    exit 1
fi

if [[ -z "${remote_file}" ]]; then
    remote_file=${local_file}
fi

#
# Finally diff them!
#

diff_files
