diff --git a/contrib/devtools/github-release.sh b/contrib/devtools/github-release.sh new file mode 100755 --- /dev/null +++ b/contrib/devtools/github-release.sh @@ -0,0 +1,241 @@ +#!/bin/bash + +set -u + +SCRIPT_PATH="$(cd "$(dirname "$0")"; pwd)" +ORIGINAL_PWD=`pwd` +TOPLEVEL="$(cd "$SCRIPT_PATH"; echo `git rev-parse --show-toplevel`)" +OAUTH_TOKEN_PATH="${PWD}/.github-oauth-token" +TAG="" +TAG_COMMIT="" + +help_message() { + echo "Create a draft Github release and upload binaries." + echo "Usage: $0 " + echo "-a, --asset-dir (required) Path to the top-level directory outputted by a Gitian build." + echo " This directory must contain linux, osx, and win binaries in those respective sub-directories." + echo "-d, --dry-run Run through the script, but do not touch existing tags, push to Github, or upload release files." + echo "-h, --help Display this help message." + echo "-o, --oauth-token Path to a file containing your OAuth token (defaults to: '${OAUTH_TOKEN_PATH}')." + echo "-t, --tag (required) The tag to create and release." + echo "-T, --tag-commit Override the commit to tag. If not set, HEAD will be tagged if a tag does not already exist." + echo " If set, the given commit will be tagged after any existing tag of the same name has been deleted." + exit 0 +} + +ASSET_DIR="" +DRY_RUN="false" + +# Parse command line arguments +while [[ $# -gt 0 ]]; do +case $1 in + -a|--assets-dir) + ASSET_DIR=$(cd $2; pwd) + shift # shift past argument + shift # shift past value + ;; + -d|--dry-run) + DRY_RUN="true" + shift # shift past argument + ;; + -h|--help) + help_message + shift # shift past argument + ;; + -o|--oauth-token) + OAUTH_TOKEN_PATH="$2" + shift # shift past argument + shift # shift past value + ;; + -t|--tag) + TAG="$2" + shift # shift past argument + shift # shift past value + ;; + -T|--tag-commit) + TAG_COMMIT="$2" + shift # shift past argument + shift # shift past value + ;; + *) + echo "Unknown argument: $1" + help_message + shift # skip argument + ;; +esac +done + +# Sanity checks on the release tag +if [ -z "$TAG" ]; then + echo "Error: The release tag was not set. Try setting it with [ -t | --tag ]" + exit 10 +fi +TAG_PATTERN="^v[0-9]*\.[0-9]*\.[0-9]*" +if [[ ! $TAG =~ $TAG_PATTERN ]]; then + echo "Error: Tag '$TAG' does not match the versioning pattern '$TAG_PATTERN'" + exit 11 +fi +VERSION=`echo "${TAG}" | cut -c 2-` + +# Fetch OAuth token +if [ ! -f "$OAUTH_TOKEN_PATH" ]; then + echo "Error: OAuth token file '${OAUTH_TOKEN_PATH}' does not exist" + exit 20 +fi +OAUTH_TOKEN=`cat "${OAUTH_TOKEN_PATH}"` +if [ -z "$OAUTH_TOKEN" ]; then + echo "Error: OAuth token is empty" + exit 21 +fi + +# Collect list of assets (binaries) to upload +if [ -z "$ASSET_DIR" ]; then + echo "Error: Asset directory was not set. Try setting it with [ -a | --asset-dir ]" + exit 30 +fi +ASSET_LIST=() +if [ -d "$ASSET_DIR" ]; then + LINUX_BINARIES="${ASSET_DIR}/linux/bitcoin-abc-${VERSION}-*" + for ASSET_FILE in $LINUX_BINARIES; do + if [[ ! $ASSET_FILE =~ "debug" ]]; then + if [ -f "$ASSET_FILE" ]; then + ASSET_LIST+=("$ASSET_FILE") + fi + fi + done + + OSX_BINARY="${ASSET_DIR}/osx/bitcoin-abc-${VERSION}-osx-unsigned.dmg" + if [ -f "$OSX_BINARY" ]; then + ASSET_LIST+=("$OSX_BINARY") + fi + + WIN_BINARIES="${ASSET_DIR}/win/bitcoin-abc-${VERSION}-*-setup-unsigned.exe" + for ASSET_FILE in $WIN_BINARIES; do + if [ -f "$ASSET_FILE" ]; then + ASSET_LIST+=("$ASSET_FILE") + fi + done +else + echo "Error: Asset directory '$ASSET_DIR' does not exist" + exit 31 +fi + +# Fetch release notes +RELEASE_NOTES=`cat "${TOPLEVEL}/doc/release-notes/release-notes-${VERSION}.md" | jq -Rs '.'` +if [ "$RELEASE_NOTES" == '""' ]; then + echo "Error: Could not fetch release notes for version '${VERSION}'" + exit 40 +fi + +# Default tag commit to HEAD if it wasn't set +cd "$SCRIPT_PATH" +if [ -z "$TAG_COMMIT" ]; then + TAG_COMMIT="HEAD" +else + if [ "$DRY_RUN" == "false" ]; then + # This is a noop if the tag doesn't exist + git tag -d "$TAG" + else + echo "Dry run note: Git tag '${TAG}' would be deleted if it exists." + fi +fi + +# Create the release tag +if [ "$DRY_RUN" == "false" ]; then + git tag "$TAG" "$TAG_COMMIT" + if [ "$?" -ne 0 ]; then + echo "Warning: Tag already exists and no override was given. Creating a new release for the existing tag '${TAG}'..." + fi +else + echo "Dry run note: Git tag '${TAG}' would be created at commit '${TAG_COMMIT}' if the tag does not exist." +fi + +# Fetch remote repo to see if any commits need to be pushed. Normally, we don't +# need to check ahead of time to see if commits need to be pushed. But, in the +# case that we are re-publishing a release, attempting to push an old tag will +# fail to fast-forward, despite the remote being up-to-date (and we do not want +# to erase any history). +GIT_REPO="https://${OAUTH_TOKEN}@github.com/bitcoin-abc/bitcoin-abc.git" +git fetch "$GIT_REPO" +COMMITS_TO_PUSH=$(git log "FETCH_HEAD..${TAG}") + +# Push upstream changes up to that tag +GIT_OPTIONS="" +if [ "$DRY_RUN" == "false" ]; then + GIT_OPTIONS="--dry-run" +fi +if [ ! -z "$COMMITS_TO_PUSH" ]; then + git push $GIT_OPTIONS "$GIT_REPO" "${TAG}:master" + PUSH_EXIT_CODE=$? + if [ "$PUSH_EXIT_CODE" -ne 0 ]; then + echo "Error: Git pushing commits exited with code: $PUSH_EXIT_CODE" + exit 50 + fi +else + echo "Git remote is already up-to-date. Skipping pushing of commits." +fi + +# Push the tag +git push $GIT_OPTIONS "$GIT_REPO" "$TAG" +PUSH_TAG_EXIT_CODE=$? +if [ "$PUSH_TAG_EXIT_CODE" -ne 0 ]; then + echo "Error: Git pushing tag '${TAG}' exited with code: $PUSH_TAG_EXIT_CODE" + exit 51 +fi +cd "$ORIGINAL_PWD" + +# Format request data +POST_DATA="{\"tag_name\": \"${TAG}\", \"name\": \"${VERSION}\", \"body\": ${RELEASE_NOTES}, \"draft\": true}" +URL="https://api.github.com/repos/bitcoin-abc/bitcoin-abc/releases" + +if [ "$DRY_RUN" == "true" ]; then + echo "POST request data that would be sent to '${URL}':" + printf '%s\n' "$POST_DATA" + echo + if [ ${#ASSET_LIST[@]} -gt 0 ]; then + echo "Would attempt to upload these files:" + for FILENAME in "${ASSET_LIST[@]}"; do + echo "$FILENAME" + done + else + echo "Warning: Would attempt to upload no files" + fi +else + echo "Creating draft release..." + RESPONSE=`curl -X POST -H "Content-Type: application/json" -H "Authorization: token ${OAUTH_TOKEN}" -d "${POST_DATA}" "${URL}"` + CURL_EXIT_CODE=$? + echo "Curl response:" + echo "$RESPONSE" + if [ "$CURL_EXIT_CODE" -ne 0 ]; then + echo "On requesting '${URL}'" + echo "Curl exited with code: ${CURL_EXIT_CODE}" + exit 60 + fi + RELEASE_ID=`echo "$RESPONSE" | jq '.id'` + JQ_EXIT_CODE=$? + if [ "$JQ_EXIT_CODE" -ne 0 ]; then + echo "Error: jq failed with exit code: ${JQ_EXIT_CODE}" + exit 61 + fi + UPLOAD_URL="https://uploads.github.com/repos/bitcoin-abc/bitcoin-abc/releases/${RELEASE_ID}/assets" + + echo "Uploading assets..." + ASSET_COUNTER=0 + if [ ${#ASSET_LIST[@]} -gt 0 ]; then + for FILEPATH in "${ASSET_LIST[@]}"; do + echo "Uploading '${FILEPATH}'..." + FILENAME=`basename "${FILEPATH}"` + UPLOAD_RESPONSE=`curl -X POST -H "Content-Type:application/octet-stream" -H "Authorization: token ${OAUTH_TOKEN}" --data-binary "@${FILEPATH}" "${UPLOAD_URL}?name=${FILENAME}"` + if [ "$?" == 0 ]; then + ASSET_COUNTER=$((ASSET_COUNTER + 1)) + else + echo "Upload of '${FILEPATH}' failed." + echo "Curl response:" + echo "$UPLOAD_RESPONSE" + fi + done + fi + echo "Uploaded ${ASSET_COUNTER} assets." +fi + +echo "Done."