#/usr/bin/env bash
#
# ad_hoc_batch.sh : creates batches given a list of PRs
#
# e.g.
#
# $ BASEBRANCH=different_base ad_hoc_batch.sh "#50839, #51695, #51969"
#
#  produces:
#
#   rfagen/ad_hoc_without_50839
#     containing
#       rfagen/ad_hoc_/51695 rfagen/ad_hoc_/51969
#
#   rfagen/ad_hoc_without_51695
#     containing
#       rfagen/ad_hoc_/50839 rfagen/ad_hoc_/51969
#
#   rfagen/ad_hoc_without_51969
#     containing
#       rfagen/ad_hoc_/50839 rfagen/ad_hoc_/51695
#
# all based off of the branch "origin/different_base"
#
# CAVEATS:
#  * this doesn't do much error checking on merges for conflicts, etc.
#    as this is generally going to be used to dissect a buildable batch,
#    all of these PRs generally play well together at a git level
#
#  * if the first of the "PR numbers" begins with "batch", then
#    it will not be fetched and will not be merged.
#
#    e.g. ./ad_hoc_batch.sh --pulls batch-foobarbaz 1, 2, 3
#       will result in a single branch made up of BASEBRANCH + PRs 1, 2 and 3
#       and the name will be rfagen/ad_hoc_batch-foobarbaz
#
# PERFORMANCE:
#  * local testing showed:
#      3 PRs could build 3 branches in about 30 seconds (10 sec/PR)
#      11 PRs took about 133 seconds (12 sec/PR)

PRLIST=""
PRCOUNT=0
USEALL=1
RUNONE=0
MULTI=0
DRYRUN=0
NOPUSH=0
BASEBRANCH=vehicle-merge-queue-batch-base
SETBB=0
BASESHA=" "
SETBS=0
PLANNER_MESSSAGE=0

function printerror (){
  echo "*****************************"
  echo -e $*
  echo "*****************************"
}

function usage () {
  echo " "
  echo "usage: $0 [--help][--exclude-one|--multisect|--include-all][--base-branch <BRANCH>|--base-sha <SHA>] --pulls <PR#> <PR#> [<PR#> ...]"
  echo "where:"
  echo "        --help          displays this"
  echo " "
  echo " "
  echo "        required:"
  echo "        --pulls         list of pull requests for the batch"
  echo " "
  echo " "
  echo "        zero or one of:"
  echo "        --exclude-one   create one batch. Exclude the first PR from the --pulls list."
  echo "                        the batch will be named '<userid>/ad_hoc_without_<PRnumber>'"
  echo " "
  echo "        --multisect     create a set of batches equal to the number of PRs."
  echo "                        each of the batches will be missing one of the PRs."
  echo "                        each batch will be named '<userid>/ad_hoc_without_<PRnumber>'"
  echo " "
  echo "        (default)       if neither are specified, create one batch. Include all the PRs from the --pulls list."
  echo "                        the batch will be named '<userid>/ad_hoc_including_all"
  echo " "
  echo " "
  echo "        zero or one of:"
  echo "        --base-branch   use the base branch <BRANCH> for the initial merge instead of"
  echo "                        the default of vehicle-merge-queue-base"
  echo "        --base-sha      use the commit at <SHA> for the initial merge instead of the default"
  echo " "
  echo " "
  echo "        optional:"
  echo "        --dry-run       displays a list of the PRs and SHAs that would be fetched and used but does"
  echo "                        not create the branchnes and does not push them"
  echo " "
  echo "        --nopush        perform the merging but do not push the resulting branch(es) to origin"
  echo " "
  echo "        --planner-msg   use Planner style commit messages for merges"
  exit 1
}

if [[ $(git status --porcelain | grep -c "^ M") -gt 0 ]]; then
  MODDED="$(git status --porcelain | grep "^ M" | sed 's/^ M/\\n/')"
  printerror "There are modified files present that will be overwritten:\n$MODDED"
  exit 1
fi

while [[ $# -gt 0 ]]; do
  case $1 in
    --help)
      usage
      ;;
    --pulls)
      shift
      ISPR=$1
      while [[ "${ISPR:0:2}" != "--" &&  $# -gt 0 ]]; do
        PRLIST+=" $ISPR"
        let PRCOUNT+=1
        shift
        ISPR=${1:-}
      done
      ;;
    --multisect)
      USEALL=0
      MULTI=1
      if [[ $RUNONE -ne 0 ]]; then
        printerror "--multisect and --exclude-one are mutually exclusive"
        usage
      fi
      shift
      ;;
    --exclude-one)
      USEALL=0
      RUNONE=1
      if [[ $MULTI -ne 0 ]]; then
        printerror "--multisect and --exclude-one are mutually exclusive"
        usage
      fi
      shift
      ;;
    --base-branch)
      SETBB=1
      if [[ $SETBS -ne 0 ]]; then
        printerror "--base-branch and --base-sha are mutually exclusive"
        usage
      fi
      shift
      BASEBRANCH=$1
      shift
      ;;
    --base-sha)
      SETBS=1
      if [[ $SETBB -ne 0 ]]; then
        printerror "--base-branch and --base-sha are mutually exclusive"
        usage
      fi
      shift
      BASESHA=$1
      shift
      ;;
    --dry-run)
      DRYRUN=1
      shift
      ;;
    --nopush)
      NOPUSH=1
      shift
      ;;
    --planner-msg)
      PLANNER_MESSSAGE=1
      shift
      ;;
    *)
      printerror "Unrecognized parameter(s): '$*'"
      usage
      ;;
  esac
done

function log_msg () {
    echo "$(date +%s) $*"
    echo "$*" >> $LOGNAME 2>&1
    echo "$(date +%s) ****************************************" >> $LOGNAME 2>&1
}

LOGNAME="$(date +%s)-ad_hoc_batch.log"
STARTTIME="$(date +%s)"
AD_HOC_BRANCH_BASE="$(whoami)/ad_hoc"

#validating git access
log_msg "Fetching origin/master to validate access to the driving repo"
git fetch origin master
if [[ $? -ne 0 ]]; then
  echo "git fetch unsuccessful!"
  usage
fi

CLEAN_PRS=" "
for PR in $PRLIST
do
    CLEAN_PRS+="$(echo $PR | sed 's/#//g;s/,//g') "
done

CLEAN_PRS=($CLEAN_PRS)
let PR_COUNT=${#CLEAN_PRS[*]}

if [[ $PRCOUNT -lt 2 && $USEALL -ne 1 ]]
then
    printerror "please include at least two PRs"
    usage
fi

if [[ $SETBS -eq 1 ]]; then
  BASEBRANCH=" "
fi

log_msg "cleaning up, checking out and updating $BASEBRANCH"
CLEANUPS="$(git branch --format="%(refname:short)" --list ${AD_HOC_BRANCH_BASE}* $BASEBRANCH)"
if [[ "$CLEANUPS" != "" ]]; then
  if [[ "$DRYRUN" != "1" ]]; then
    git checkout --detach >> $LOGNAME 2>&1
    git branch -D $CLEANUPS >> $LOGNAME 2>&1
  else
    echo "Would clean up these branches:"
    echo "$CLEANUPS"
  fi
fi
if [[ "$DRYRUN" != "1" ]]; then
  if [[ $SETBS -eq 1 ]]; then
    git fetch -f origin $BASESHA >> $LOGNAME 2>&1
  else
    git fetch -f origin $BASEBRANCH:$BASEBRANCH >> $LOGNAME 2>&1
  fi
  if [[ $? -ne 0 ]]; then
    printerror "git fetch unsuccessful for $BASESHA $BASEBRANCH!"
    exit 1
  fi
else
  echo "Would fetch $BASEBRANCH $BASESHA as the base for merging"
fi

for PR in ${CLEAN_PRS[*]}
do
  log_msg "fetching PR $PR"
  if [[ "$DRYRUN" != "1" ]]; then
    git fetch -f origin pull/$PR/head:$AD_HOC_BRANCH_BASE/$PR >> $LOGNAME 2>&1
    if [[ $? -ne 0 ]]; then
      printerror "git fetch unsuccessful for $PR !"
      exit 1
    fi
  fi
done

set -euo pipefail

for EXCLUDE_PR in ${CLEAN_PRS[*]}
do
    if [[ $USEALL -eq 1 ]]; then
      AD_HOC_BRANCH="${AD_HOC_BRANCH_BASE}_including_all"
      EXCLUDE_PR=nothing
    else
      AD_HOC_BRANCH="${AD_HOC_BRANCH_BASE}_without_$EXCLUDE_PR"
    fi
    log_msg  "Excluding $EXCLUDE_PR, setting up $BASEBRANCH"
    if [[ "$DRYRUN" != "1" ]]; then
      if [[ $SETBS -eq 1 ]]; then
        git checkout $BASESHA >> $LOGNAME 2>&1
      else
        git checkout $BASEBRANCH >> $LOGNAME 2>&1
        git reset --hard $BASEBRANCH >> $LOGNAME 2>&1
      fi
      git checkout -b $AD_HOC_BRANCH >> $LOGNAME 2>&1
    else
      echo "Would have checked out $AD_HOC_BRANCH at $BASEBRANCH $BASESHA"
    fi
    SO_FAR_PRS=" "
    for PR in ${CLEAN_PRS[*]/$EXCLUDE_PR}; do
      SO_FAR_PRS+="$PR "
      if [[ $PLANNER_MESSSAGE -eq 1 ]]; then
        if [[ "$DRYRUN" != "1" ]]; then
          MESSAGE="$PR: $(git log -1 --format='%d' $AD_HOC_BRANCH_BASE/$PR)"
        else
          MESSAGE="$PR: git log -1 --format='%d' $AD_HOC_BRANCH_BASE/$PR"
        fi
      else
        MESSAGE="Merge ${SO_FAR_PRS}"
      fi
      if [[ "$DRYRUN" != "1" ]]; then
        log_msg "merging $PR"
        git merge --no-ff --no-edit --message "$MESSAGE" $AD_HOC_BRANCH_BASE/$PR >> $LOGNAME 2>&1
        if [[ $? -ne 0 ]]; then
          log_msg "ERROR merging $PR -- review $LOGNAME for details"
          exit 1
        fi
        log_msg "Printing log of $AD_HOC_BRANCH + $PR"
        if [[ $SETBS -eq 1 ]]; then
          git log --decorate --oneline --graph HEAD...$BASESHA >> $LOGNAME 2>&1
        else
          git log --decorate --oneline --graph HEAD...$BASEBRANCH >> $LOGNAME 2>&1
        fi
      else
        echo "Would have merged $PR into $AD_HOC_BRANCH while excluding $EXCLUDE_PR"
        echo "    with commit message: $MESSAGE"
      fi
    done
    if [[ "$NOPUSH" != "1" ]]; then
      if [[ "$DRYRUN" != "1" ]]; then
        log_msg "pushing $AD_HOC_BRANCH to origin"
        git push -f origin --set-upstream $AD_HOC_BRANCH >> $LOGNAME 2>&1
      else
        echo "Would have pushed $AD_HOC_BRANCH excluding $EXCLUDE_PR"
      fi
    else
      log_msg "NOT pushing $AD_HOC_BRANCH to origin, as requested"
    fi
    if [[ $RUNONE -eq 1 || $USEALL -eq 1 ]]; then
      break
    fi
done
ENDTIME="$(date +%s)"
log_msg  "process took $(($ENDTIME-$STARTTIME)) seconds"
echo log found in $(pwd)/$LOGNAME
