#!/bin/bash

### This script is used for synchronizing the current repository into a local VMR.
### It pulls the current repository's code into the specified VMR directory for local testing or
### Source-Build validation.
###
### The tooling used for synchronization will clone the VMR repository into a temporary folder if
### it does not already exist. These clones can be reused in future synchronizations, so it is
### recommended to dedicate a folder for this to speed up re-runs.
###
### USAGE:
###   Synchronize current repository into a local VMR:
###     ./vmr-sync.sh --tmp "$HOME/repos/tmp" "$HOME/repos/dotnet"
###
### Options:
###   -t, --tmp, --tmp-dir PATH
###       Required. Path to the temporary folder where repositories will be cloned
###
###   -b, --branch, --vmr-branch BRANCH_NAME
###       Optional. Branch of the 'dotnet/dotnet' repo to synchronize. The VMR will be checked out to this branch
###
###   --debug
###       Optional. Turns on the most verbose logging for the VMR tooling
###
###   --remote name:URI
###       Optional. Additional remote to use during the synchronization
###       This can be used to synchronize to a commit from a fork of the repository
###       Example: 'runtime:https://github.com/yourfork/runtime'
###
###   --azdev-pat
###       Optional. Azure DevOps PAT to use for cloning private repositories.
###
###   -v, --vmr, --vmr-dir PATH
###       Optional. Path to the dotnet/dotnet repository. When null, gets cloned to the temporary folder

source="${BASH_SOURCE[0]}"

# resolve $source until the file is no longer a symlink
while [[ -h "$source" ]]; do
  scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
  source="$(readlink "$source")"
  # if $source was a relative symlink, we need to resolve it relative to the path where the
  # symlink file was located
  [[ $source != /* ]] && source="$scriptroot/$source"
done
scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"

function print_help () {
    sed -n '/^### /,/^$/p' "$source" | cut -b 5-
}

COLOR_RED=$(tput setaf 1 2>/dev/null || true)
COLOR_CYAN=$(tput setaf 6 2>/dev/null || true)
COLOR_CLEAR=$(tput sgr0 2>/dev/null || true)
COLOR_RESET=uniquesearchablestring
FAILURE_PREFIX='> '

function fail () {
  echo "${COLOR_RED}$FAILURE_PREFIX${1//${COLOR_RESET}/${COLOR_RED}}${COLOR_CLEAR}" >&2
}

function highlight () {
  echo "${COLOR_CYAN}$FAILURE_PREFIX${1//${COLOR_RESET}/${COLOR_CYAN}}${COLOR_CLEAR}"
}

tmp_dir=''
vmr_dir=''
vmr_branch=''
additional_remotes=''
verbosity=verbose
azdev_pat=''
ci=false

while [[ $# -gt 0 ]]; do
  opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")"
  case "$opt" in
    -t|--tmp|--tmp-dir)
      tmp_dir=$2
      shift
      ;;
    -v|--vmr|--vmr-dir)
      vmr_dir=$2
      shift
      ;;
    -b|--branch|--vmr-branch)
      vmr_branch=$2
      shift
      ;;
    --remote)
      additional_remotes="$additional_remotes $2"
      shift
      ;;
    --azdev-pat)
      azdev_pat=$2
      shift
      ;;
    --ci)
      ci=true
      ;;
    -d|--debug)
      verbosity=debug
      ;;
    -h|--help)
      print_help
      exit 0
      ;;
    *)
      fail "Invalid argument: $1"
      print_help
      exit 1
      ;;
  esac

  shift
done

# Validation

if [[ -z "$tmp_dir" ]]; then
  fail "Missing --tmp-dir argument. Please specify the path to the temporary folder where the repositories will be cloned"
  exit 1
fi

# Sanitize the input

if [[ -z "$vmr_dir" ]]; then
  vmr_dir="$tmp_dir/dotnet"
fi

if [[ ! -d "$tmp_dir" ]]; then
  mkdir -p "$tmp_dir"
fi

if [[ "$verbosity" == "debug" ]]; then
  set -x
fi

# Prepare the VMR

if [[ ! -d "$vmr_dir" ]]; then
  highlight "Cloning 'dotnet/dotnet' into $vmr_dir.."
  git clone https://github.com/dotnet/dotnet "$vmr_dir"

  if [[ -n "$vmr_branch" ]]; then
    git -C "$vmr_dir" switch -c "$vmr_branch"
  fi
else
  if ! git -C "$vmr_dir" diff --quiet; then
    fail "There are changes in the working tree of $vmr_dir. Please commit or stash your changes"
    exit 1
  fi

  if [[ -n "$vmr_branch" ]]; then
    highlight "Preparing $vmr_dir"
    git -C "$vmr_dir" checkout "$vmr_branch"
    git -C "$vmr_dir" pull
  fi
fi

set -e

# Prepare darc

highlight 'Installing .NET, preparing the tooling..'
source "./eng/common/tools.sh"
InitializeDotNetCli true
GetDarc
dotnetDir=$( cd ./.dotnet/; pwd -P )
dotnet=$dotnetDir/dotnet

highlight "Starting the synchronization of VMR.."
set +e

if [[ -n "$additional_remotes" ]]; then
  additional_remotes="--additional-remotes $additional_remotes"
fi

if [[ -n "$azdev_pat" ]]; then
  azdev_pat="--azdev-pat $azdev_pat"
fi

ci_arg=''
if [[ "$ci" == "true" ]]; then
  ci_arg="--ci"
fi

# Synchronize the VMR

version_details_path=$(cd "$scriptroot/.."; pwd -P)/Version.Details.xml
repo_name=$(grep -m 1 '<Source ' "$version_details_path" | sed -n 's/.*Mapping="\([^"]*\)".*/\1/p')
if [[ -z "$repo_name" ]]; then
  fail "Failed to resolve repo mapping from $version_details_path"
  exit 1
fi

export DOTNET_ROOT="$dotnetDir"

"$darc_tool" vmr forwardflow \
  --tmp "$tmp_dir"             \
  $azdev_pat                   \
  --$verbosity                 \
  $ci_arg                      \
  $additional_remotes          \
  "$vmr_dir"

if [[ $? == 0 ]]; then
  highlight "Synchronization succeeded"
else
  highlight "Failed to flow code into the local VMR. Falling back to resetting the VMR to match repo contents..."
  git -C "$vmr_dir" reset --hard

  "$darc_tool" vmr reset \
    "$repo_name:HEAD"                              \
    --vmr "$vmr_dir"                               \
    --tmp "$tmp_dir"                               \
    --additional-remotes "$repo_name:$repo_root"

  if [[ $? == 0 ]]; then
    highlight "Successfully reset the VMR using 'darc vmr reset'"
  else
    fail "Synchronization of repo to VMR failed!"
    fail "'$vmr_dir' is left in its last state (re-run of this script will reset it)."
    fail "Please inspect the logs which contain path to the failing patch file (use --debug to get all the details)."
    fail "Once you make changes to the conflicting VMR patch, commit it locally and re-run this script."
    exit 1
  fi
fi
