#!/bin/bash set -eu cd "$( dirname "${BASH_SOURCE[0]}" )/.." version= version_next= main() { local opt_auto=0 while [[ $# -gt 0 ]] ; do case $1 in -auto) opt_auto=1 ;; *) echo "Usage: $0 [-auto]" >&2 exit 0 ;; esac shift done if [[ "$opt_auto" -eq 1 ]] ; then auto_prepare_release else interactive fi } auto_prepare_release() { echo 'script/release: auto mode for CI, will check or modify version based on tag' >&2 assert_tree_clean local is_tag=0 local version_tag= if version_tag=$(git describe --candidates=0 --tags HEAD 2>/dev/null) ; then is_tag=1 version_tag=${version_tag##v} version_check "$version_tag" fi local last_tag= local version_replace= if [[ "$is_tag" -eq 0 ]] ; then last_tag=$(git tag --sort=-version:refname |head -n1) last_tag=${last_tag##v} version_replace="${last_tag}.post$(date -u +%y%m%d%H%M)" update_version "setup.py" "s/VERSION =.+/VERSION = '$version_replace'/" update_version "python2/httplib2/__init__.py" "s/__version__ =.+/__version__ = '$version_replace'/" update_version "python3/httplib2/__init__.py" "s/__version__ =.+/__version__ = '$version_replace'/" version_check "$version_replace" fi } interactive() { echo 'script/release: interactive mode for creating new tagged releases with human assistance' >&2 local branch="${1-$(git symbolic-ref --short HEAD)}" version="$(PYTHONPATH=$PWD/python3 python3 -c 'import httplib2; print(httplib2.__version__)')" printf "\nbranch: %s httplib2.__version__: '%s'\n" $branch $version >&2 if [[ "$branch" != "master" ]] ; then echo "Must be on master" >&2 exit 1 fi assert_tree_clean last_commit_message=$(git show --format="%s" --no-patch HEAD) expect_commit_message="v$version release" if [[ "$last_commit_message" != "$expect_commit_message" ]] ; then printf "Last commit message: '%s' expected: '%s'\n" "$last_commit_message" "$expect_commit_message" >&2 if confirm "Create release commit? [yN] " ; then create_commit elif ! confirm "Continue without proper release commit? [yN] " ; then exit 1 fi fi confirm "Continue? [yN] " || exit 1 echo "Creating tag v$version" >&2 if ! git tag "v$version" ; then echo "git tag failed " >&2 confirm "Continue still? [yN] " || exit 1 fi echo "Building package" >&2 find . -name '*.pyc' -o -name '*.pyo' -o -name '*.orig' -delete rm -rf python{2,3}/.cache rm -rf build dist # TODO: sdist bdist_wheel # but wheels don't roll well with our 2/3 split code base local venv=./venv-release if [[ ! -d "$venv" ]] ; then virtualenv $venv $venv/bin/pip install -U pip setuptools wheel twine fi $venv/bin/python setup.py sdist if confirm "Upload to PyPI? Use in special situation, normally CI (Travis) will upload to PyPI. [yN] " ; then $venv/bin/twine upload dist/* || exit 1 fi git push --tags } create_commit() { echo "" >&2 echo "Plan:" >&2 echo "1. bump version" >&2 echo "2. update CHANGELOG" >&2 echo "3. commit" >&2 echo "4. run bin/release again" >&2 echo "" >&2 bump_version edit_news git diff confirm "Ready to commit? [Yn] " || exit 1 git commit -a -m "v$version_next release" echo "Re-exec $0 to continue" >&2 exec $0 } bump_version() { local current=$version echo "Current version: '$current'" >&2 echo -n "Enter next version (empty to abort): " >&2 read version_next if [[ -z "$version_next" ]] ; then exit 1 fi echo "Next version: '$version_next'" >&2 update_version "python3/httplib2/__init__.py" "s/__version__ =.+/__version__ = '$version_next'/" update_version "python2/httplib2/__init__.py" "s/__version__ =.+/__version__ = '$version_next'/" update_version "setup.py" "s/VERSION =.+/VERSION = '$version_next'/" confirm "Confirm changes? [yN] " || exit 1 } update_version() { local path="$1" local sed_expr="$2" # sed -E --in-place='' -e "s/VERSION =.+/VERSION = '$version_replace'/" setup.py # sed -E --in-place='' -e "s/__version__ =.+/__version__ = '$version_replace'/" python2/httplib2/__init__.py python3/httplib2/__init__.py echo "Updating file '$path'" >&2 if ! sed -E --in-place='' -e "$sed_expr" "$path" ; then echo "sed error $?" >&2 exit 1 fi assert_modified "$path" echo "" >&2 } edit_news() { echo "Changes since last release:" >&2 git log --format='%h %an %s' "v$version"^.. -- || exit 1 echo "" >&2 patch -p1 </dev/null) [[ -z "$editor" ]] && editor="$EDITOR" if [[ -n "$editor" ]] ; then if confirm "Open default editor for CHANGELOG? [Yn] " ; then $editor CHANGELOG else confirm "Edit CHANGELOG manually and press any key" fi else echo "Unable to determine default text editor." >&2 confirm "Edit CHANGELOG manually and press any key" fi echo "" >&2 assert_modified CHANGELOG echo "" >&2 confirm "Confirm changes? [yN] " || exit 1 } assert_modified() { local path="$1" if git diff --exit-code "$path" ; then echo "File '$path' is not modified" >&2 exit 1 fi } assert_tree_clean() { if [[ -n "$(git status --short -uall)" ]] ; then echo "Tree must be clean. git status:" >&2 echo "" >&2 git status --short -uall echo "" >&2 exit 1 fi } version_check() { local need=$1 local version_setup=$(fgrep 'VERSION =' setup.py |tr -d " '" |cut -d\= -f2) local version_py2=$(cd python2 ; python2 -Es -c 'import httplib2;print(httplib2.__version__)') local version_py3=$(cd python3 ; python3 -Es -c 'import httplib2;print(httplib2.__version__)') if [[ "$version_setup" != "$need" ]] ; then echo "error: setup.py VERSION=$version_setup expected=$need" >&1 exit 1 fi if [[ "$version_py2" != "$need" ]] ; then echo "error: python2/httplib2/__init__.py:__version__=$version_py2 expected=$need" >&1 exit 1 fi if [[ "$version_py3" != "$need" ]] ; then echo "error: python3/httplib2/__init__.py:__version__=$version_py3 expected=$need" >&1 exit 1 fi } confirm() { local reply local prompt="$1" read -n1 -p "$prompt" reply >&2 echo "" >&2 rc=0 local default_y=" \[Yn\] $" if [[ -z "$reply" ]] && [[ "$prompt" =~ $default_y ]] ; then reply="y" fi [[ "$reply" != "y" ]] && rc=1 return $rc } main "$@"