#!/bin/sh

# Outputs information about a CI job, when run under Azure Pipelines,
# CircleCI, GitHub Actions, or Travis CI.

# On CircleCI, it depends on environment variable CIRCLE_COMPARE_URL, which is set
# for CircleCI v2 and earlier.  To set CIRCLE_COMPARE_URL on V2.1 and later (per
# https://discuss.circleci.com/t/is-circle-compare-url-still-a-thing/33366):
# jobs:
#   my-job:
#     environment:
#       CIRCLE_COMPARE_URL: << pipeline.project.git_url >>/compare/<< pipeline.git.base_revision >>..<<pipeline.git.revision>>

# Typical use:
#
#   if [ -d /tmp/$USER/plume-scripts ] ; then
#     git -C /tmp/$USER/plume-scripts pull -q > /dev/null 2>&1
#   else
#     mkdir -p /tmp/$USER && git -C /tmp/$USER clone --depth=1 -q https://github.com/plume-lib/plume-scripts.git
#   fi
#   eval $(/tmp/$USER/plume-scripts/ci-info DEFAULT-ORGANIZATION)
#
# Here are the variables that it sets:
# CI_IS_PULL_REQUEST: Either "true" or "false" (without the quotes).
# CI_ORGANIZATION: The GitHub organization.  In a PR, refers to the source or head.
#   Optional argument DEFAULT-ORGANIZATION is used if no organization can be determined.
# CI_BRANCH_NAME: The name of the source/head branch (in a PR) or of the branch being tested.
# CI_COMMIT_RANGE: An argument to `git diff` with the range of commits IDs.
#    (Don't use it with `git log`, which interprets its argument differently.)
# CI_COMMIT_RANGE_START: The start element of CI_COMMIT_RANGE, a commit ID.
# CI_COMMIT_RANGE_END: The end element of CI_COMMIT_RANGE, a commit ID.
#
# For example use, see comments in file `lint-diff.py` in this directory.
#
# Requires the `jq` program to be installed, when used in Azure Pipelines jobs.

DEBUG=""
if [ "$1" = "--verbose" ]; then
  VERBOSE="--verbose"
  shift
fi
if [ "$1" = "--debug" ]; then
  DEBUG="--debug"
  VERBOSE="--verbose"
  shift
fi
if [ "$#" -gt 1 ]; then
  echo "echo Usage: $0 [--verbose] [--debug] [DEFAULT-ORGANIZATION];"
  echo "Usage: $0 [--verbose] [--debug] [DEFAULT-ORGANIZATION]" >&2
  echo "exit 2"
  exit 2
fi

# SCRIPTDIR="$( cd "$(dirname "$0")" ; pwd -P )"

if [ "$DEBUG" = "--debug" ]; then
  echo
  env | sort | sed -e 's/^/echo /'
  echo
  git --no-pager branch -a | sed -e 's/^/echo /'
  echo
  git --no-pager log --graph | head --lines=1000 | sed -e 's/^/echo /'
  echo
fi

### Functions

# Returns either `git rev-parse HEAD^1` if HEAD^1 exists, or `git rev-parse HEAD`.
# HEAD^1 does not exist if the clone was created with `git clone --depth=1`.
head1() {
  if git rev-parse --verify --quiet HEAD^1 > /dev/null 2>&1; then
    git rev-parse HEAD^1
  else
    git rev-parse HEAD
  fi
}

### Organization

## Continuous integration services
if [ "$TRAVIS" = "true" ]; then
  CI_ORGANIZATION=${TRAVIS_PULL_REQUEST_SLUG%/*}
  if [ "$CI_ORGANIZATION" = "" ]; then
    CI_ORGANIZATION=${TRAVIS_REPO_SLUG%/*}
  fi
elif [ -n "$AZURE_HTTP_USER_AGENT" ]; then
  if [ "$BUILD_REASON" = "PullRequest" ]; then
    SLUG=$(wget -q -O - "https://api.github.com/repos/${BUILD_REPOSITORY_NAME}/pulls/${SYSTEM_PULLREQUEST_PULLREQUESTNUMBER}" | jq .head.label | sed 's/"//g')
    CI_ORGANIZATION=${SLUG%:*}
  else
    CI_ORGANIZATION=${BUILD_REPOSITORY_NAME%/*}
    # CI_REPO=${BUILD_REPOSITORY_NAME##*/}
  fi
elif [ -n "$CIRCLE_PR_USERNAME" ]; then
  CI_ORGANIZATION="$CIRCLE_PR_USERNAME"
fi

## Git clone
if [ "$CI_ORGANIZATION" = "" ]; then
  URL=$(git config --get remote.origin.url)
  # This handles two possible forms for a URL:
  #   https://github.com/mernst/annotation-tools
  #   git@github.com:mernst/annotation-tools.git
  SLUG=${URL#https://github.com/}
  SLUG=${SLUG#git@github.com:}
  CI_ORGANIZATION=${SLUG%/*}
  # TODO: Maybe add a sanity check here.
fi

## Default
if [ "$CI_ORGANIZATION" = "" ]; then
  CI_ORGANIZATION=$1
fi

### Other information (besides organization)

## Both of these commands for DEFAULT_BRANCH_NAME seem to work.
# DEFAULT_BRANCH_NAME=$(git ls-remote --symref "$(git config --get remote.origin.url)" HEAD | awk '/^ref:/ {sub(/refs\/heads\//, "", $2); print $2}')
DEFAULT_BRANCH_NAME=$(git remote show origin | grep 'HEAD branch' | cut -d' ' -f5)
if [ "$DEBUG" = "--debug" ]; then
  echo "echo remote's DEFAULT_BRANCH_NAME=$DEFAULT_BRANCH_NAME;"
  echo "HEAD of ${URL} = $(git ls-remote --symref "${URL}" HEAD)" | sed -e 's/^/echo /'
  echo "echo current branch: $(git branch --show-current)"
  echo "echo current repo: $(git config --get remote.origin.url)"
fi

if [ -n "$SYSTEM_PULLREQUEST_TARGETBRANCH" ]; then
  ## Azure Pipelines pull request
  if [ "$DEBUG" = "--debug" ]; then
    echo "echo Azure Pipelines pull request $SYSTEM_PULLREQUEST_TARGETBRANCH;"
  fi
  CI_IS_PULL_REQUEST=true
  # WARNING!  $BUILD_SOURCEBRANCHNAME is just a name, not a full ref path.  For example,
  # $BUILD_SOURCEBRANCHNAME may be "merge" when $BUILD_SOURCEBRANCH is "refs/pull/2971/merge".
  CI_BRANCH_NAME=$SYSTEM_PULLREQUEST_SOURCEBRANCH
  # For CI_COMMIT_RANGE_START:  HEAD = $BUILD_SOURCEVERSION is a commit created by Azure; it isn't
  # in the repo.  Its first child HEAD^1 is the target branch (e.g., master); use that instead of
  # $SYSTEM_PULLREQUEST_TARGETBRANCH is not fetched into this repo.
  CI_COMMIT_RANGE_START=$(head1)
  CI_COMMIT_RANGE_END=$SYSTEM_PULLREQUEST_SOURCECOMMITID
  CI_COMMIT_RANGE=${CI_COMMIT_RANGE_START}...${CI_COMMIT_RANGE_END}
elif [ -n "$BUILD_SOURCEBRANCH" ]; then
  # Azure Pipelines build for a branch (possibly master).
  if [ "$DEBUG" = "--debug" ]; then
    echo "echo Azure Pipelines build for a branch $BUILD_SOURCEBRANCH;"
    echo "echo SYSTEM_PULLREQUEST_TARGETBRANCH=$SYSTEM_PULLREQUEST_TARGETBRANCH is not set but BUILD_SOURCEBRANCH=$BUILD_SOURCEBRANCH;"
  fi
  CI_IS_PULL_REQUEST=false
  # In Azure Pipelines:  BUILD_SOURCEBRANCH is a full ref path like "refs/heads/mybranch";
  # BUILD_SOURCEBRANCHNAME is a short name like "mybranch"; $BUILD_SOURCEVERSION is a commit ID.
  # some clients such as `git-clone-related` MUST be given a branch name, not a commit ID.
  CI_BRANCH_NAME=$BUILD_SOURCEBRANCHNAME
  if [ "$BUILD_SOURCEBRANCHNAME" = "$DEFAULT_BRANCH_NAME" ]; then
    DEFAULT_CI_COMMIT_RANGE_START=$(head1)

    # If a build fails, we would like to continue failing subsequent
    # builds until the problem is fixed.  Using `git rev-parse HEAD^1`
    # does not do that, because there could have been multiple pushes
    # since the last successful CI job.
    # Pass argument to ci-last-success.py, because in a pull request
    # we are only interested in the last success on the master branch,
    # not on the feature branch!  The feature branch might have many
    # succeeding jobs and then ci-last-success.py will return just
    # part of the pull request's diffs.

    ## # ci-last-success.py currently doesn't work because api.github.com returns "state: pending".
    # # TODO: wrap all this in a ci-last-commit.sh shell script.
    # if pip3 list --format columns | tail -n +1 | grep -q '^requests ' ; then
    #   # echo "echo \"Python requests package is already installed\";"
    #   :
    # else
    #   (sudo pip3 install requests || pip3 install --user requests || true) > /dev/null 2>&1
    # fi
    # if pip3 list --format columns | tail -n +1 | grep -q '^requests ' ; then
    #   CI_COMMIT_RANGE_START=$(${SCRIPTDIR}/ci-last-success.py ${CI_ORGANIZATION} ${CI_REPO} ${DEFAULT_CI_COMMIT_RANGE_START})
    #   if [ "$CI_COMMIT_RANGE_START" = "" ] ; then
    #     echo "echo \"WARNING: ci-last-success.py script failed; just considering last commit.\";"
    #     CI_COMMIT_RANGE_START=${DEFAULT_CI_COMMIT_RANGE_START}
    #   elif ! git cat-file -e ${CI_COMMIT_RANGE_START}^{commit}; then
    #     # The commit is not in the repository.  Maybe it is older than the
    #     # commits pulled into this repository.  This can happen when when
    #     # api.github.com incorrectly returns "state: pending" for successful
    #     # jobs.
    #     echo "echo \"WARNING: ci-last-success.py script returned commit not in repository; using last commit.\";"
    #     CI_COMMIT_RANGE_START=${DEFAULT_CI_COMMIT_RANGE_START}
    #   else
    #     echo "echo \"ci-last-success.py ${CI_ORGANIZATION} ${CI_REPO} succeeded with $CI_COMMIT_RANGE_START.\";"
    #   fi
    # else
    #   echo "echo \"WARNING: Could not run ci-last-success.py script; just considering last commit.\";"
    #   CI_COMMIT_RANGE_START=${DEFAULT_CI_COMMIT_RANGE_START}
    # fi

    ## Use this because ci-last-success.py currently doesn't work.
    CI_COMMIT_RANGE_START=${DEFAULT_CI_COMMIT_RANGE_START}
  else
    git fetch origin "$DEFAULT_BRANCH_NAME"
    CI_COMMIT_RANGE_START=$(git rev-parse "origin/$DEFAULT_BRANCH_NAME")
  fi
  CI_COMMIT_RANGE_END=$BUILD_SOURCEVERSION
  CI_COMMIT_RANGE=${CI_COMMIT_RANGE_START}...${CI_COMMIT_RANGE_END}
elif [ "$TRAVIS" = "true" ]; then
  ## Travis CI
  if [ "$DEBUG" = "--debug" ]; then
    echo "echo Travis CI $TRAVIS;"
  fi
  CI_IS_PULL_REQUEST=$TRAVIS_PULL_REQUEST
  CI_BRANCH_NAME=${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH}
  if [ "$VERBOSE" = "--verbose" ]; then
    echo "echo TRAVIS_PULL_REQUEST_BRANCH=$TRAVIS_PULL_REQUEST_BRANCH;"
    echo "echo TRAVIS_BRANCH=$TRAVIS_BRANCH;"
    echo "echo CI_BRANCH_NAME=$CI_BRANCH_NAME;"
  fi
  # $TRAVIS_COMMIT_RANGE is empty for builds triggered by the initial commit of a new branch.
  CI_COMMIT_RANGE=$TRAVIS_COMMIT_RANGE

elif [ -n "$CIRCLE_COMPARE_URL" ]; then
  ## CircleCI v2 or earlier, or CIRCLE_COMPARE_URL was set for compatibility.
  if [ "$DEBUG" = "--debug" ]; then
    echo "echo CircleCI $CIRCLE_COMPARE_URL $CIRCLE_PULL_REQUEST;"
  fi
  if [ -n "$CIRCLE_PULL_REQUEST" ]; then
    CI_IS_PULL_REQUEST=true
  else
    CI_IS_PULL_REQUEST=false
  fi
  CI_COMMIT_RANGE=$(echo "${CIRCLE_COMPARE_URL}" | cut -d/ -f7)
  case $CI_COMMIT_RANGE in
    *"..."*)
      CI_COMMIT_RANGE_START=$(git rev-parse "${COMMIT_RANGE}")
      CI_COMMIT_RANGE_END=$(git rev-parse "${COMMIT_RANGE}")
      CI_COMMIT_RANGE="${CI_COMMIT_RANGE_START}...${CI_COMMIT_RANGE_END}"
      ;;
  esac
  # CIRCLE_BRANCH may be "pull/327"
  case "$CIRCLE_BRANCH" in
    pull/*)
      CI_BRANCH_NAME=$(curl -s "https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/pulls/${CIRCLE_PR_NUMBER}" | jq -r '.head.ref')
      ;;
    *)
      CI_BRANCH_NAME=$CIRCLE_BRANCH
      ;;
  esac

elif [ -n "$GITHUB_HEAD_REF" ]; then
  # GitHub Actions pull request (no special handling is required for non-PR GitHub Actions jobs).
  if [ "$DEBUG" = "--debug" ]; then
    echo "echo GitHub Actions pull request https://github.com/${GITHUB_REPOSITORY}/pull/${GITHUB_REF};"
    echo "echo   base_ref=${GITHUB_BASE_REF}, head_ref=${GITHUB_HEAD_REF};"
    echo "echo   pwd=$(pwd);"
  fi

  CI_IS_PULL_REQUEST=true
  CI_BRANCH_NAME="${GITHUB_HEAD_REF}"
  CI_COMMIT_RANGE_START=$(git rev-parse "${GITHUB_BASE_REF}")
  CI_COMMIT_RANGE_END=$(git rev-parse "${GITHUB_HEAD_REF}")
  CI_COMMIT_RANGE="${CI_COMMIT_RANGE_START}...${CI_COMMIT_RANGE_END}"

else
  # It's not a pull request (except maybe a re-run Azure Pipelines pull request).
  if [ "$DEBUG" = "--debug" ]; then
    if [ -n "$CIRCLE_PULL_REQUEST" ]; then
      echo "echo CircleCI pull request with CIRCLE_COMPARE_URL not set: $CIRCLE_PULL_REQUEST;"
    else
      echo "echo Else clause: no CI environment detected;"
    fi
  fi
  CI_IS_PULL_REQUEST=false
  # git 2.22 and later has `git branch --show-current`; CircleCI doesn't have that version yet.
  CI_BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
  # In an Azure Pipelines pull request, `git branch` yields "(HEAD detached at pull/4/merge)".
  # If you re-run a pull request via "Queue" rather than "Rebuild",
  # variables SYSTEM_PULLREQUEST_TARGETBRANCH and SYSTEM_PULLREQUEST_SOURCEBRANCH are not set.
  if [ "$CI_BRANCH_NAME" = '(HEAD' ]; then
    CI_BRANCH_NAME=$DEFAULT_BRANCH_NAME
  fi
fi

if [ "$DEBUG" = "--debug" ]; then
  echo "echo CI_IS_PULL_REQUEST=${CI_IS_PULL_REQUEST};"
  echo "echo CI_BRANCH_NAME=${CI_BRANCH_NAME};"
  echo "echo CI_COMMIT_RANGE_START=${CI_COMMIT_RANGE_START};"
  echo "echo CI_COMMIT_RANGE_END=${CI_COMMIT_RANGE_END};"
  echo "echo CI_COMMIT_RANGE=${CI_COMMIT_RANGE};"
fi

## The above may have not set CI_COMMIT_RANGE.  Set it.

# Separate from "It's not a pull request" because sometimes this is not set for Travis.
if [ -z "$CI_COMMIT_RANGE" ]; then
  if [ "$DEBUG" = "--debug" ]; then
    echo "echo Setting CI_COMMIT_RANGE from CI_BRANCH_NAME=$CI_BRANCH_NAME;"
  fi
  if [ "$CI_BRANCH_NAME" = "$DEFAULT_BRANCH_NAME" ]; then
    if git show --summary HEAD | grep -q ^Merge:; then
      CI_COMMIT_RANGE_START=$(git merge-base HEAD^1 HEAD^2)
    else
      CI_COMMIT_RANGE_START=$(head1)
    fi
    CI_COMMIT_RANGE_END=$(git rev-parse HEAD)
  else
    if git rev-parse --verify --quiet "origin/$DEFAULT_BRANCH_NAME" > /dev/null 2>&1; then
      CI_COMMIT_RANGE_START=$(git rev-parse "origin/$DEFAULT_BRANCH_NAME")
    else
      if git show --summary HEAD | grep -q ^Merge:; then
        CI_COMMIT_RANGE_START=$(git merge-base HEAD^1 HEAD^2)
      else
        CI_COMMIT_RANGE_START=$(head1)
      fi
    fi
    CI_COMMIT_RANGE_END=$(git rev-parse "$CI_BRANCH_NAME")
  fi
  CI_COMMIT_RANGE=${CI_COMMIT_RANGE_START}...${CI_COMMIT_RANGE_END}
fi

if [ -z "$CI_COMMIT_RANGE_START" ]; then
  if [ "$DEBUG" = "--debug" ]; then
    echo "echo Setting CI_COMMIT_RANGE_START from CI_COMMIT_RANGE=$CI_COMMIT_RANGE;"
  fi
  CI_COMMIT_RANGE_START=${CI_COMMIT_RANGE%%.*}
  CI_COMMIT_RANGE_END=${CI_COMMIT_RANGE##*.}
  if [ -z "$CI_COMMIT_RANGE_START" ]; then
    CI_COMMIT_RANGE_START=${CI_COMMIT_RANGE_END}
  fi
fi

# Avoid errors regarding empty range
if [ "$CI_COMMIT_RANGE_START" = "$CI_COMMIT_RANGE_END" ]; then
  if [ "$VERBOSE" = "--verbose" ]; then
    echo "echo Resetting CI_COMMIT_RANGE_START because CI_COMMIT_RANGE=$CI_COMMIT_RANGE;"
  fi
  if git show --summary "$CI_COMMIT_RANGE_END" | grep -q ^Merge:; then
    CI_COMMIT_RANGE_START=$(git merge-base "$CI_COMMIT_RANGE_END^1" "$CI_COMMIT_RANGE_END^2")
  else
    if git rev-parse --verify --quiet "$CI_COMMIT_RANGE_END^1" > /dev/null 2>&1; then
      CI_COMMIT_RANGE_START=$(git rev-parse "$CI_COMMIT_RANGE_END^1")
    fi
  fi
  CI_COMMIT_RANGE="${CI_COMMIT_RANGE_START}...${CI_COMMIT_RANGE_END}"
fi

# The diff might be empty if the last commit is the revert of the one before it.

### Print it out

if [ "$VERBOSE" = "--verbose" ]; then
  echo "echo CI_ORGANIZATION=$CI_ORGANIZATION;"
fi
echo "CI_ORGANIZATION=$CI_ORGANIZATION; export CI_ORGANIZATION;"
if [ "$VERBOSE" = "--verbose" ]; then
  echo "echo CI_IS_PULL_REQUEST=$CI_IS_PULL_REQUEST;"
fi
echo "CI_IS_PULL_REQUEST=$CI_IS_PULL_REQUEST; export CI_IS_PULL_REQUEST;"
if [ "$VERBOSE" = "--verbose" ]; then
  echo "echo CI_BRANCH_NAME=$CI_BRANCH_NAME;"
fi
echo "CI_BRANCH_NAME=$CI_BRANCH_NAME; export CI_BRANCH_NAME;"
if [ "$VERBOSE" = "--verbose" ]; then
  echo "echo CI_COMMIT_RANGE=$CI_COMMIT_RANGE;"
fi
echo "CI_COMMIT_RANGE=$CI_COMMIT_RANGE; export CI_COMMIT_RANGE;"
if [ "$VERBOSE" = "--verbose" ]; then
  echo "echo CI_COMMIT_RANGE_START=$CI_COMMIT_RANGE_START;"
fi
echo "CI_COMMIT_RANGE_START=$CI_COMMIT_RANGE_START; export CI_COMMIT_RANGE_START;"
if [ "$VERBOSE" = "--verbose" ]; then
  echo "echo CI_COMMIT_RANGE_END=$CI_COMMIT_RANGE_END;"
fi
echo "CI_COMMIT_RANGE_END=$CI_COMMIT_RANGE_END; export CI_COMMIT_RANGE_END;"
