#!/usr/bin/env bash
#
# Change files modification time to their last commit date
#

# Bash unofficial strict mode
set -euo pipefail
IFS=$'\n\t'

if [[ "${1:-}" == "--newer" ]]; then
  op=le
  shift
else
  op=eq
fi

# BSD systems
if date -j &>/dev/null; then
  stat_flags="-f %m"
  date_flags="-r"
else
  # Non-BSD systems
  stat_flags="-c %Y"
  date_flags="-d@"
fi

bash_opts=()
if bash --help 2>&1 | grep -q -- '--noprofile'; then
  bash_opts+=(--noprofile)
fi
if bash --help 2>&1 | grep -q -- '--norc'; then
  bash_opts+=(--norc)
fi

status_opts=(--porcelain --short)
# %ct: committer date, UNIX timestamp / %at: author date, UNIX timestamp
whatchanged_opts=(--format='%ct')
if git status --help 2>&1 | grep -q -- "--no-renames"; then
  status_opts+=(--no-renames)
  whatchanged_opts+=(--no-renames)
fi
if git status --help 2>&1 | grep -q -- "--untracked-files"; then
  status_opts+=(--untracked-files=no)
fi
if git status --help 2>&1 | grep -q -- "--ignored"; then
  status_opts+=(--ignored=no)
fi

prefix="$(git rev-parse --show-prefix) "
strip="${#prefix}"

tmpfile=$(mktemp)
# shellcheck disable=SC2064
trap "rm -f '${tmpfile}'" 0

awk_flags=(
  -F'\t'
  -v date_flags="${date_flags}"
  -v op="${op}"
  -v stat_flags="${stat_flags}"
  -v strip="${strip}"
  -v tmpfile="${tmpfile}"
)

# sanity check, not required:
if awk --help 2>&1 | grep -q -- '--posix'; then
  awk_flags+=(--posix)
fi

read -r -d '' awk_script <<"EOF" || true
BEGIN {
  seen[""]=1
  print "#!/usr/bin/env bash"
  print "set +e"
  print "t() {"
  print " test -e \"$2\" || return 0"
  printf(" test \"$(stat %s \"$2\" 2>/dev/null)\" -%s \"$1\" && return 0\n", stat_flags, op)
  if (date_flags == "-d@") {
    print " echo \"+ touch -h -d@$1 $2\""
    print " touch -h -d@$1 \"$2\""
  } else {
    print " t=$(date -r$1 \"+%Y%m%d%H%M.%S\")"
    print " echo \"+ touch -h -t $t $2\""
    print " touch -h -t $t \"$2\""
  }
  print "}"
}
FILENAME==tmpfile {
  skip[$1]=1
  next
}
# skip blank lines
!/^$/ {
  # skip deletes
  if (substr($1, length($1), 1) ~ /D/) {
    next
  }
  if (NF == 1) {
    ct=$1
    next
  }
  $2 = substr($2, strip, length($2)- strip + 1)
  if ($2 in seen) {
    next
  }
  if ($2 in skip) {
    next
  }
  seen[$2]=1
  # remove enclosing double quotes that git adds:
  if (substr($2, 1, 1) == "\"" && substr($2, length($2), 1) == "\"") {
    $2 = substr($2, 2, length($2) - 2)
    # unescape remaining double quotes
    gsub(/\\"/, "\"", $2)
    # unescape escaped backslashes
    gsub(/\\\\/, "\\", $2)
  }
  # escape apostrophes: ' => '\''
  gsub(/'/, "'\\''", $2)
  printf("t %s '%s'\n", ct, $2)
}
EOF

# prefix is stripped:
git --no-pager status "${status_opts[@]}" . \
  | cut -c 4- >"${tmpfile}"

# prefix is not stripped:
git --no-pager whatchanged "${whatchanged_opts[@]}" . \
  | awk "${awk_flags[@]}" "${awk_script}" "${tmpfile}" - \
  | BASH_ENV='' bash "${bash_opts[@]}" -
