#!/bin/bash

## Copyright (C) 2026 - 2026 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

## No 'set -x': xtrace prints every command's EXPANDED form to the terminal,
## which would echo an untrusted ref name (git permits U+202E and other spoofing
## codepoints in ref names) RAW, before any scan or stcat neutralization runs.
set -o errexit
set -o nounset
set -o pipefail
set -o errtrace
shopt -s inherit_errexit
shopt -s shift_verbose
export LC_ALL=C

true "INFO: Currently running script: ${BASH_SOURCE[0]} $*"

# shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/has.bsh
source "${HELPER_SCRIPTS_PATH:-}"/usr/libexec/helper-scripts/has.bsh
# shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/log_run_die.sh
source "${HELPER_SCRIPTS_PATH:-}"/usr/libexec/helper-scripts/log_run_die.sh

has stcat || die 1 "'stcat' not on PATH."
has unicode-show || die 1 "'unicode-show' not on PATH."

if [ "$#" -lt 1 ]; then
   die 2 "usage: ${0##*/} [git-diff-option...] <ref-or-range>"
fi

review_spec="${!#}"
review_opts=( "${@:1:$#-1}" )

if [[ "${review_spec}" == -* ]]; then
   die 2 "ref '${review_spec}' must not start with '-'."
fi

## Don't allow the use of '--' as it could cause options to be misinterpreted
## by the various Git tools.
for review_opt in "${review_opts[@]}"; do
   if [ "${review_opt}" = '--' ]; then
      die 2 "'--' is not a valid option here, ref-or-range is always the last argument."
   fi
done

## Allow both ranges ('..') and merge-base ranges ('...').
if [[ "${review_spec}" == *...* ]]; then
   range_op='...'
   range_base="${review_spec%%...*}"
   range_target="${review_spec#*...}"
elif [[ "${review_spec}" == *..* ]]; then
   range_op='..'
   range_base="${review_spec%%..*}"
   range_target="${review_spec#*..}"
else
   range_op='...'
   range_base=''
   range_target="${review_spec}"
fi
if [ -z "${range_target}" ]; then
   die 2 "ref/range '${review_spec}' has no target ref."
fi
if [ -z "${range_base}" ]; then
   range_base='HEAD'
fi

## Resolve all refs once to avoid confusing results if someone pulls or
## otherwise changes refs mid-review. Always resolve into a form that can be
## used with '..' in each tool to reduce tool calling complexity.
if ! target_commit="$(git rev-parse --verify --end-of-options "${range_target}^{commit}" 2>/dev/null)"; then
   die 2 "ref '${range_target}' does not resolve to a commit."
fi
if ! base_commit="$(git rev-parse --verify --end-of-options "${range_base}^{commit}" 2>/dev/null)"; then
   die 2 "base ref '${range_base}' does not resolve to a commit."
fi
if [ "${range_op}" = '...' ]; then
   if ! base_commit="$(git merge-base --end-of-options "${base_commit}" "${target_commit}")"; then
      die 2 "Could not find merge base between '${base_commit}' and '${target_commit}'."
   fi
fi

## Handle the result of a unicode-spoofing scan ($1 = its exit code, $2 = its
## name). The scan tools separate their exit codes on purpose:
##   0    = clean;
##   1    = a look-alike was found;
##   >= 2 = the scan could not run (bad/absent ref, no new commits, not a work
##          tree, git failure).
## prompt rc: 0 = continue; 1 = declined; 2 = no controlling terminal to ask (a
## CI/batch run) -> fail closed.
review_prompt_or_die() {
   local scan_rc="$1" scan_name="$2" prompt_rc
   if [ "${scan_rc}" -eq 0 ]; then
      return 0
   fi
   if [ "${scan_rc}" -ge 2 ]; then
      die "${scan_rc}" "'${scan_name}' could not run (rc='${scan_rc}'); see its error above. Failing closed."
   fi
   log warn "'${scan_name}' flagged possible unicode spoofing (rc='${scan_rc}')."
   prompt_rc=0
   prompt_yes_no_tty "Continue the review anyway?" || prompt_rc="$?"
   if [ "${prompt_rc}" -eq 2 ]; then
      die "${scan_rc}" "'${scan_name}' flagged spoofing and there is no terminal to ask; failing closed."
   fi
   if [ "${prompt_rc}" -ne 0 ]; then
      die "${scan_rc}" "aborting the review at your request."
   fi
   log info "continuing despite '${scan_name}' (rc='${scan_rc}')."
}

## Scan the ref's new commits (content, messages, author identity)...
scan_rc=0
check-ref-commits-for-unicode "${target_commit}" "${base_commit}" || scan_rc="$?"
review_prompt_or_die "${scan_rc}" check-ref-commits-for-unicode

## ...and every ref NAME in the repo, for spoofing via non-ASCII unicode.
## check-ref-names-for-unicode takes ref-name GLOBS (git for-each-ref), not a
## single ref -- with no argument it scans all refs (its documented default).
## Passing the reviewed ref instead would only check that one name (missing a
## spoofed sibling), and a look-alike name would make for-each-ref fail to
## match ("No refs matched") rather than be flagged as suspicious. Scanning
## every ref catches a spoofed name anywhere the fetch introduced one.
scan_rc=0
check-ref-names-for-unicode || scan_rc="$?"
review_prompt_or_die "${scan_rc}" check-ref-names-for-unicode

review_range="${base_commit}..${target_commit}"

git --no-pager log "${review_range}" | NO_COLOR=1 stcat

git-diff-review "${review_opts[@]}" "${review_range}"

git-meld "${review_opts[@]}" "${review_range}"

git-kdiff3 "${review_opts[@]}" "${review_range}"

true "$0: OK."
