#!/usr/bin/env bash
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
source "$PLUGIN_AVAILABLE_PATH/apps/functions"
source "$PLUGIN_AVAILABLE_PATH/config/functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x

use_git_worktree() {
  declare desc="detects whether to use git worktree"
  local GIT_VERSION MAJOR_VERSION MINOR_VERSION

  GIT_VERSION=$(git --version | awk '{split($0,a," "); print a[3]}')
  MAJOR_VERSION=$(echo "$GIT_VERSION" | awk '{split($0,a,"."); print a[1]}')
  MINOR_VERSION=$(echo "$GIT_VERSION" | awk '{split($0,a,"."); print a[2]}')

  if [[ "$MAJOR_VERSION" -ge "3" ]]; then
    return 0
  elif [[ "$MAJOR_VERSION" -eq "2" ]] && [[ "$MINOR_VERSION" -ge "11" ]]; then
    return 0
  else
    return 1
  fi
}

git_build_app_repo() {
  declare desc="builds local git app repo for app"
  declare APP="$1" REV="$2"
  local DOKKU_GLOBAL_DISABLE_AUTOCREATE

  verify_app_name "$APP"

  # clean up after ourselves
  local GIT_BUILD_APP_REPO_TMP_WORK_DIR=$(mktemp -d "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX")
  trap "rm -rf '$GIT_BUILD_APP_REPO_TMP_WORK_DIR' >/dev/null" RETURN INT TERM EXIT

  local TMP_TAG="dokku/$REV"
  chmod 755 "$GIT_BUILD_APP_REPO_TMP_WORK_DIR"
  unset GIT_DIR GIT_WORK_TREE

  ! plugn trigger app-exists "$APP" >/dev/null 2>&1 && plugn trigger app-maybe-create "$APP"

  if use_git_worktree; then
    # git worktree - this method uses git worktree which was introduced in git 2.5
    pushd "$DOKKU_ROOT/$APP" >/dev/null
    # unset the git quarantine path to allow us to use 2.13.0+
    # See this issue for more information: https://github.com/dokku/dokku/issues/2796
    suppress_output env -u GIT_QUARANTINE_PATH git worktree add "$GIT_BUILD_APP_REPO_TMP_WORK_DIR" "$REV"
    popd >/dev/null 2>&1 || pushd "/tmp" >/dev/null
    pushd "$GIT_BUILD_APP_REPO_TMP_WORK_DIR" >/dev/null
  else
    # git clone - this method creates a new git repository and adds the primary
    #             repo as a remote, then does a fetch depth=1 to avoid cloning
    #             the entire repo.
    #             Not working for git >= 2.11.0 due to changes introduced
    #             in this merge:
    #             https://github.com/git/git/commit/25ab004c53cdcfea485e5bf437aeaa74df47196d
    pushd "$GIT_BUILD_APP_REPO_TMP_WORK_DIR" >/dev/null
    GIT_DIR="$DOKKU_ROOT/$APP" git tag -d "$TMP_TAG" &>/dev/null || true
    GIT_DIR="$DOKKU_ROOT/$APP" git tag "$TMP_TAG" "$REV" &>/dev/null
    git init &>/dev/null
    git config advice.detachedHead false
    git remote add origin "$DOKKU_ROOT/$APP" &>/dev/null
    git fetch --depth=1 origin "refs/tags/$TMP_TAG" &>/dev/null
    git reset --hard FETCH_HEAD &>/dev/null
    GIT_DIR="$DOKKU_ROOT/$APP" git tag -d "$TMP_TAG" &>/dev/null || true
  fi

  suppress_output env -u GIT_QUARANTINE_PATH git submodule update --init --recursive
  local DOKKU_KEEP_GIT_DIR="$(fn-plugin-property-get "git" "$APP" "keep-git-dir" "")"
  if [[ "$DOKKU_KEEP_GIT_DIR" != "true" ]]; then
    find . -name .git -prune -exec rm -rf {} \; >/dev/null
  fi

  if use_git_worktree; then
    pushd "$DOKKU_ROOT/$APP" >/dev/null
    git worktree prune
    popd >/dev/null
  fi

  local DOKKU_APP_DISABLE_ANSI_PREFIX_REMOVAL DOKKU_GLOBAL_DISABLE_ANSI_PREFIX_REMOVAL DOKKU_DISABLE_ANSI_PREFIX_REMOVAL
  DOKKU_APP_DISABLE_ANSI_PREFIX_REMOVAL=$(config_get "$APP" DOKKU_DISABLE_ANSI_PREFIX_REMOVAL || true)
  DOKKU_GLOBAL_DISABLE_ANSI_PREFIX_REMOVAL=$(config_get --global DOKKU_DISABLE_ANSI_PREFIX_REMOVAL || true)
  DOKKU_DISABLE_ANSI_PREFIX_REMOVAL=${DOKKU_APP_DISABLE_ANSI_PREFIX_REMOVAL:="$DOKKU_GLOBAL_DISABLE_ANSI_PREFIX_REMOVAL"}

  if [[ "$DOKKU_DISABLE_ANSI_PREFIX_REMOVAL" == "true" ]]; then
    git_trigger_build "$APP" "$GIT_BUILD_APP_REPO_TMP_WORK_DIR" "$REV"
  else
    git_trigger_build "$APP" "$GIT_BUILD_APP_REPO_TMP_WORK_DIR" "$REV" | sed -u "s/^/"$'\e[1G'"/"
  fi
}

git_trigger_build() {
  declare desc="triggers the actual build process for a given app within a directory at a particular revision"
  declare APP="$1" TMP_WORK_DIR="$2" REV="$3"

  plugn trigger post-extract "$APP" "$TMP_WORK_DIR" "$REV"
  if [[ -f Dockerfile ]] && [[ "$(
    [[ -f .env ]] && grep -q BUILDPACK_URL .env
    echo $?
  )" != "0" ]] && [[ ! -f ".buildpacks" ]] && [[ -z $(config_get "$APP" BUILDPACK_URL || true) ]]; then
    plugn trigger pre-receive-app "$APP" "dockerfile" "$TMP_WORK_DIR" "$REV"
    dokku_receive "$APP" "dockerfile" "$TMP_WORK_DIR"
  else
    plugn trigger pre-receive-app "$APP" "herokuish" "$TMP_WORK_DIR" "$REV"
    dokku_receive "$APP" "herokuish" "$TMP_WORK_DIR"
  fi
}

git_deploy_branch() {
  declare desc="retrieve the deploy branch for a given application"
  declare cmd="git-hook"
  local APP="$1"

  local DOKKU_DEPLOY_BRANCH="$(fn-plugin-property-get "git" "$APP" "deploy-branch" "")"
  local DOKKU_GLOBAL_DEPLOY_BRANCH="$(fn-plugin-property-get "git" "--global" "deploy-branch" "")"
  if [[ -n "$DOKKU_DEPLOY_BRANCH" ]]; then
    echo "$DOKKU_DEPLOY_BRANCH"
  elif [[ -n "$DOKKU_GLOBAL_DEPLOY_BRANCH" ]]; then
    echo "$DOKKU_GLOBAL_DEPLOY_BRANCH"
  else
    echo "master"
  fi
}

cmd-git-hook() {
  declare desc="kick off receive-app trigger from git prereceive hook"
  declare cmd="git-hook"
  [[ "$1" == "$cmd" ]] && shift 1
  declare APP="$1"
  local DOKKU_DEPLOY_BRANCH

  is_valid_app_name "$APP"

  DOKKU_DEPLOY_BRANCH="$(git_deploy_branch "$APP")"
  if ! git check-ref-format --branch "$DOKKU_DEPLOY_BRANCH" >/dev/null 2>&1; then
    echo $'\e[1G\e[K'"-----> WARNING: Invalid branch name '$DOKKU_DEPLOY_BRANCH' specified via DOKKU_DEPLOY_BRANCH."
    echo $'\e[1G\e[K'"-----> For more details, please see the man page for 'git-check-ref-format.'"
    return
  fi

  local oldrev newrev refname
  while read -r oldrev newrev refname; do
    # Only run this script for the master branch. You can remove this
    # if block if you wish to run it for others as well.
    if [[ $refname == "refs/heads/${DOKKU_DEPLOY_BRANCH}" ]]; then
      # broken out into plugin so we might support other methods to receive an app
      # shellcheck disable=SC2086
      git_receive_app "$APP" "$newrev"
    else
      if [[ $(find "$PLUGIN_PATH"/enabled/*/receive-branch 2>/dev/null | wc -l) != 1 ]]; then
        # shellcheck disable=SC2086
        plugn trigger receive-branch $APP $newrev $refname
      else
        echo $'\e[1G\e[K'"-----> WARNING: deploy did not complete, you must push to ${DOKKU_DEPLOY_BRANCH}."
        echo $'\e[1G\e[K'"-----> for example, try 'git push <dokku> ${refname/refs\/heads\//}:${DOKKU_DEPLOY_BRANCH}'"
      fi
    fi
  done
}

git_build() {
  declare desc="setup and call git_build_app_repo"
  local APP="$1" REV="$2"
  local DOKKU_DEPLOY_BRANCH ENV_VAR_NAME REF
  if [[ $# -ge 2 ]]; then
    ENV_VAR_NAME="$(fn-plugin-property-get "git" "$APP" "rev-env-var")"
    if [[ -z "$ENV_VAR_NAME" ]] && ! fn-plugin-property-exists "git" "$APP" "rev-env-var"; then
      ENV_VAR_NAME="GIT_REV"
    fi

    if [[ -n "$ENV_VAR_NAME" ]]; then
      DOKKU_QUIET_OUTPUT=1 config_set --no-restart "$APP" "${ENV_VAR_NAME}=${REV}"
    fi
    local REF="$REV"
  else
    DOKKU_DEPLOY_BRANCH="$(git_deploy_branch "$APP")"
    REF=$(<"$DOKKU_ROOT/$APP/refs/heads/$DOKKU_DEPLOY_BRANCH")
  fi

  # shellcheck disable=SC2086
  git_build_app_repo $APP $REF
}

git_receive_app() {
  declare desc="git receive-app plugin trigger"
  declare APP="$1" REV="$2"

  # Don't trigger git build if there is no git repository.
  if [[ ! -d "$DOKKU_ROOT/$APP/refs" ]]; then
    true
  else
    acquire_app_deploy_lock "$APP" "exclusive"
    # shellcheck disable=SC2086
    git_build $APP $REV
    release_app_deploy_lock "$APP"
  fi
}

cmd-git-upload-pack() {
  declare desc="executes git-upload-pack"
  declare cmd="git-upload-pack"
  [[ "$1" == "$cmd" ]] && shift 1
  declare APP="$1"

  APP="$(echo "$APP" | perl -pe 's/(?<!\\)'\''//g' | sed 's/\\'\''/'\''/g' | sed 's/^\///g')"
  is_valid_app_name "$APP"
  ! plugn trigger app-exists "$APP" >/dev/null 2>&1 && plugn trigger app-maybe-create "$APP"

  plugn trigger git-pre-pull "$APP"
  cat | git-upload-pack "$DOKKU_ROOT/$APP"
  plugn trigger git-post-pull "$APP"
}

cmd-git-glob() {
  declare desc="catch-all for any other git-* commands"
  declare cmd="git-*"
  local APP="$(echo "$2" | perl -pe 's/(?<!\\)'\''//g' | sed 's/\\'\''/'\''/g' | sed 's/^\///g')"
  local APP_PATH=$DOKKU_ROOT/$APP
  is_valid_app_name "$APP"

  if [[ $1 == "git-receive-pack" && ! -d "$APP_PATH/refs" ]]; then
    ! plugn trigger app-exists "$APP" >/dev/null 2>&1 && plugn trigger app-maybe-create "$APP"

    fn-git-create-hook "$APP"
  fi

  if [[ $1 == "git-receive-pack" ]]; then
    local args="$1 '$APP_PATH'"
  else
    local args=$*
  fi
  git-shell -c "$args"
}

fn-git-create-hook() {
  declare APP="$1"
  local APP_PATH="$DOKKU_ROOT/$APP"
  local PRERECEIVE_HOOK="$APP_PATH/hooks/pre-receive"
  verify_app_name "$APP"

  if [[ ! -d "$APP_PATH/refs" ]]; then
    git init --bare "$APP_PATH" >/dev/null
  fi

  cat >"$PRERECEIVE_HOOK" <<EOF
#!/usr/bin/env bash
set -e
set -o pipefail

cat | DOKKU_ROOT="$DOKKU_ROOT" dokku git-hook $APP
EOF
  chmod +x "$PRERECEIVE_HOOK"
}
