#!/bin/bash
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
# This script is used to create a release candidate. It will update the current
# .parquetcppversion as well as creates a tag for the new release candidate and
# publishes the source distribution and signatures to be voted on.
#
#   master~1 (0.5.0-snapshot) ----- master (0.6.0-snapshot)
#                             \---- 0.5.0 (0.5.0)
#
# A email template will be generated after successfully generating a release
# candidate which will need to be sent to the dev@ and private@ mailing lists.
#
set -o errexit
set -o nounset

rc_number=0
override_version=''
parquetcpp_git_web_url='https://gitbox.apache.org/repos/asf?p=parquet-cpp.git'
parquet_svn_dist_url='https://dist.apache.org/repos/dist/dev/parquet'

function print_help_and_exit {
cat <<EOF
Apache Parquet C++ release candidate tool.

Usage: $0 [-h] [-l p|m|M] [-r #] [-p | publish]

  -h  Print this help message and exit
  -l  Increment level, must be one of:
        p, patch (default)
        m, minor
        M, major
  -v  Override the existing version in .parquetcppversion
  -r  Release candidate number (default: 0)
  -p  Publish the release candidate (default: dry-run, does not publish anything)
EOF
exit 0
}

publish=0
increment_level="patch"
rc_number=0
while getopts ":hl:v:r:p" opt; do
  case $opt in
    l)
      case ${OPTARG} in
        'p' | 'patch') increment_level='patch' ;;
        'm' | 'minor') increment_level='minor' ;;
        'M' | 'major') increment_level='major' ;;
         *) echo 'Unknown increment level'; exit 1 ;;
      esac
      ;;
    r)
      rc_number=${OPTARG}
      ;;
    p)
      publish=1
      ;;
    h)
      print_help_and_exit
      ;;
    v)
      override_version=${OPTARG}
      ;;
    *  )
      echo "Unknown option: -$OPTARG"
      print_help_and_exit
      ;;
  esac
done

shift $(($OPTIND - 1))
if [[ "${1:-dry-run}" == "publish" ]]; then
  publish=1
fi

# Update local repository
git fetch --all -q
git fetch --tags -q

# Verify that this is a clean repository
if [[ -n "`git status --porcelain`" ]]; then
  echo "ERROR: Please run from a clean git repository."
  exit 1
elif [[ "`git rev-parse --abbrev-ref HEAD`" != "master" ]]; then
  echo "ERROR: This script must be run from master."
  exit 1
fi

if [[ ! -f .parquetcppversion ]]; then
  echo "Warrning: This script must be run from the root of the repository"
  exit 1
fi

# Calculate the new version string
current_version=$(cat .parquetcppversion | tr '[a-z]' '[A-Z]')
if ! [[ $current_version =~ .*-SNAPSHOT ]]; then
  echo "ERROR: .parquetcppversion is required to contain 'SNAPSHOT', it is ${current_version}"
  exit 1
else
  if [[ $override_version != "" ]]; then
    current_version=$override_version
  fi

  major=`echo $current_version | cut -d. -f1`
  minor=`echo $current_version | cut -d. -f2`
  patch=`echo $current_version | cut -d. -f3 | cut -d- -f1`

  current_version="${major}.${minor}.${patch}"

  if [[ $increment_level == "patch" ]]; then
    new_master_version="${major}.${minor}.$((patch + 1))"
  elif [[ $increment_level == "minor" ]]; then
    new_master_version="${major}.$((minor + 1)).0"
  elif [[ $increment_level == "major" ]]; then
    new_master_version="$((major + 1)).0.0"
  else
    echo "Unknown release increment ${increment_level}"
    exit 1
  fi

  new_snapshot_version="${new_master_version}-SNAPSHOT"
fi

# Add the rc tag to the current version
rc_version="${current_version}-rc${rc_number}"
version_tag="apache-parquet-cpp-${current_version}"
rc_version_tag="apache-parquet-cpp-${rc_version}"

echo
echo "Generating release candidate ${rc_version_tag}"
echo

# Make sure the tag does not exist
if git rev-parse ${version_tag} >/dev/null 2>&1; then
  echo "ERROR: tag ${version_tag} exists."
  exit 1
fi

if git rev-parse ${rc_version_tag} >/dev/null 2>&1; then
  echo "ERROR: tag ${rc_version_tag} exists."
  exit 1
fi

# Reset instructions
current_git_rev=$(git rev-parse HEAD)
function print_reset_instructions {
cat <<EOF
To roll back your local repo you will need to run:

  git checkout master
  git reset --hard ${current_git_rev}
  git tag -d ${rc_version_tag}
  git branch -D stage_${rc_version}
EOF
}

# If anything goes wrong from here then print roll back instructions before exiting.
function print_rollback_instructions {
  echo "ERROR: Looks like something has failed while creating the release candidate."
  print_reset_instructions
}
trap print_rollback_instructions EXIT

# All check are now complete, before we start alert if we are in dry-run
if [[ $publish == 0 ]]; then
  echo "Performing dry-run, run with '-p' when you are ready to run and publish a release candidate"
fi

# This should be a clean repo we are working against. Run clean just to ensure it is.
git clean -fdxq

echo "Generating changelog"
./dev/release/changelog $current_version
git add CHANGELOG
git commit -m "Updating CHANGELOG for ${current_version} release."

echo "Committing updated .parquetcppversion on master"
echo $new_snapshot_version > .parquetcppversion
git add .parquetcppversion
git commit -m "Incrementing snapshot version to ${new_snapshot_version}."

echo "Creating ${rc_version} staging branch"
git checkout -b "stage_${rc_version}"

echo "Updating .parquetcppversion on staging branch"
# Increment the version and create a branch
echo ${current_version} > .parquetcppversion
git add .parquetcppversion
git commit -m "Updating .parquetcppversion to ${current_version}."

# Build the source distribution from the new branch
echo "Building the source distribution"
dist_dir=dist
dist_name="${version_tag}"

mkdir -p ${dist_dir}
git archive --prefix=${dist_name}/ -o ${dist_dir}/${dist_name}.tar.gz HEAD
pushd ${dist_dir}
  # Sign the tarball.
  echo "Signing the distribution"
  gpg --armor --output ${dist_name}.tar.gz.asc --detach-sig ${dist_name}.tar.gz

  # Create the checksums
  echo "Creating checksums"
  gpg --print-md MD5 ${dist_name}.tar.gz > ${dist_name}.tar.gz.md5
  sha1sum ${dist_name}.tar.gz > ${dist_name}.tar.gz.sha1
  sha256sum ${dist_name}.tar.gz > ${dist_name}.tar.gz.sha256
  sha512sum ${dist_name}.tar.gz > ${dist_name}.tar.gz.sha512
popd

parquet_svn_rc_url="${parquet_svn_dist_url}/apache-parquet-cpp-${rc_version}"

# Publish release candidate to svn and commit and push the new git tag
if [[ $publish == 1 ]]; then
  echo "Publishing release candidate to ${parquet_svn_rc_url}"
  svn mkdir ${parquet_svn_rc_url} -m "parquet-cpp-${current_version} release candidate ${rc_version}"
  svn co --depth=empty ${parquet_svn_rc_url} ${dist_dir}
  pushd ${dist_dir}
  svn add ${dist_name}*
  svn ci -m "parquet-cpp-${current_version} release candidate ${rc_version}"
  popd

  echo "Creating tag ${rc_version_tag}"
  git tag -s ${rc_version_tag} \
    -m "Apache Parquet C++ ${current_version} release candidate ${rc_version}"
  git push origin "${rc_version_tag}"

  echo "Pushing updated .parquetcppversion to master"
  git checkout master
  git push origin "master:master-after-${rc_version_tag}"
fi

echo "Done creating the release candidate. The following draft email has been created"
echo "to send to the dev@parquet.apache.org mailing list"
echo

# Create the email template for the release candidate to be sent to the mailing lists.
if [[ $(uname) == Darwin ]]; then
  vote_end=$(date -v+3d)
else
  vote_end=$(date -d+3days)
fi

MESSAGE=$(cat <<__EOF__
To: dev@parquet.apache.org
Subject: [VOTE] Release Apache Parquet C++ ${current_version} RC${rc_number}

All,

I propose that we accept the following release candidate as the official
Apache Parquet C++ ${current_version} release.

Parquet C++ ${rc_version} includes the following:
---
The CHANGELOG for the release is available at:
${parquetcpp_git_web_url}&f=CHANGELOG&hb=${rc_version_tag}

The tag used to create the release candidate is:
${parquetcpp_git_web_url};a=shortlog;h=refs/tags/${rc_version_tag}

The release candidate is available at:
${parquet_svn_rc_url}/${dist_name}.tar.gz

The MD5 checksum of the release candidate can be found at:
${parquet_svn_rc_url}/${dist_name}.tar.gz.md5

The signature of the release candidate can be found at:
${parquet_svn_rc_url}/${dist_name}.tar.gz.asc

The GPG key used to sign the release are available at:
${parquet_svn_dist_url}/KEYS

The release is based on the commit hash ${current_git_rev}.

Please download, verify, and test.

The vote will close on ${vote_end}

[ ] +1 Release this as Apache Parquet C++ ${current_version}
[ ] +0
[ ] -1 Do not release this as Apache Parquet C++ ${current_version} because...

__EOF__
)

echo "--------------------------------------------------------------------------------"
echo
echo "${MESSAGE}"
echo
echo "--------------------------------------------------------------------------------"
echo

# Print reset instructions if this was a dry-run
if [[ $publish == 0 ]]; then
  echo
  echo "This was a dry run, nothing has been published."
  echo
  print_reset_instructions
fi

# Unset error message handler and exit
trap '' EXIT
exit 0
