#!/usr/bin/env bash
#
# A git commit hook that will automatically run format checking and DCO signoff
# checking before the push is successful.
#
# To enable this hook, run `bootstrap`, or run the following from the root of
# the repo. (Note that `bootstrap` will correctly install this even if this code
# is located in a submodule, while the following won't.)
#
# $ ln -s ../../support/hooks/pre-push .git/hooks/pre-push

DUMMY_SHA=0000000000000000000000000000000000000000

echo "Running pre-push check; to skip this step use 'push --no-verify'"

while read LOCAL_REF LOCAL_SHA REMOTE_REF REMOTE_SHA
do
  if [ "$LOCAL_SHA" = $DUMMY_SHA ]
  then
    # Branch deleted. Do nothing.
    exit 0
  else
    if [ "$REMOTE_SHA" = $DUMMY_SHA ]
    then
      # New branch. Verify the last commit, since this is very likely where the new code is
      # (though there is no way to know for sure). In the extremely uncommon case in which someone
      # pushes more than 1 new commit to a branch, CI will enforce full checking.
      RANGE="$LOCAL_SHA~1..$LOCAL_SHA"
    else
      # Updating branch. Verify new commits.
      RANGE="$REMOTE_SHA..$LOCAL_SHA"
    fi

    # Verify DCO signoff. We do this before the format checker, since it has
    # some probability of failing spuriously, while this check never should.
    #
    # In general, we can't assume that the commits are signed off by author
    # pushing, so we settle for just checking that there is a signoff at all.
    SIGNED_OFF=$(git rev-list --no-merges --grep "^Signed-off-by: " "$RANGE")
    NOT_SIGNED_OFF=$(git rev-list --no-merges "$RANGE" | grep -Fxv "$SIGNED_OFF")
    if [ -n "$NOT_SIGNED_OFF" ]
    then
      echo >&2 "ERROR: The following commits do not have DCO signoff:"
      while read -r commit; do
        echo "  $(git log --pretty=oneline --abbrev-commit -n 1 $commit)"
      done <<< "$NOT_SIGNED_OFF"
      exit 1
    fi

    # NOTE: The `tools` directory will be the same relative to the support
    # directory, independent of whether we're in a submodule, so no need to use
    # our `relpath` helper.
    SCRIPT_DIR="$(dirname "$(realpath "$0")")/../../tools"

    # TODO(hausdorff): We should have a more graceful failure story when the
    # user does not have all the tools set up correctly. This script assumes
    # `$CLANG_FORMAT` and `$BUILDIFY` are defined, or that the default values it
    # assumes for these variables correspond to real binaries on the system. If
    # either of these things aren't true, the check fails.
    for i in $(git diff --name-only $RANGE --diff-filter=ACMR --ignore-submodules=all 2>&1); do
      echo -ne "  Checking format for $i - "
      "$SCRIPT_DIR"/code_format/check_format.py check $i
      if [[ $? -ne 0 ]]; then
        exit 1
      fi

      echo "  Checking spelling for $i"
      "$SCRIPT_DIR"/spelling/check_spelling_pedantic.py check $i
      if [[ $? -ne 0 ]]; then
        exit 1
      fi
    done

    # TODO(mattklein123): Optimally we would be able to do this on a per-file basis.
    "$SCRIPT_DIR"/proto_format/proto_format.sh check
    if [[ $? -ne 0 ]]; then
      exit 1
    fi

    "$SCRIPT_DIR"/code_format/format_python_tools.sh check
    if [[ $? -ne 0 ]]; then
      exit 1
    fi

    # Check correctness of repositories definitions.
    echo "  Checking repositories definitions"
    if ! "$SCRIPT_DIR"/check_repositories.sh; then
      exit 1
    fi
  fi
done

exit 0
