Home | History | Annotate | Download | only in script
      1 #!/bin/bash
      2 set -eu
      3 cd "$( dirname "${BASH_SOURCE[0]}" )/.."
      4 
      5 version=
      6 version_next=
      7 
      8 main() {
      9 	local opt_auto=0
     10 	while [[ $# -gt 0 ]] ; do
     11 		case $1 in
     12 		-auto)
     13 			opt_auto=1
     14 			;;
     15 		*)
     16 			echo "Usage: $0 [-auto]" >&2
     17 			exit 0
     18 			;;
     19 		esac
     20 		shift
     21 	done
     22 	if [[ "$opt_auto" -eq 1 ]] ; then
     23 		auto_prepare_release
     24 	else
     25 		interactive
     26 	fi
     27 }
     28 
     29 auto_prepare_release() {
     30 	echo 'script/release: auto mode for CI, will check or modify version based on tag' >&2
     31 
     32 	assert_tree_clean
     33 
     34 	local is_tag=0
     35 	local version_tag=
     36 	if version_tag=$(git describe --candidates=0 --tags HEAD 2>/dev/null) ; then
     37 		is_tag=1
     38 		version_tag=${version_tag##v}
     39 		version_check "$version_tag"
     40 	fi
     41 
     42 	local last_tag=
     43 	local version_replace=
     44 	if [[ "$is_tag" -eq 0 ]] ; then
     45 		last_tag=$(git tag --sort=-version:refname |head -n1)
     46 		last_tag=${last_tag##v}
     47 		version_replace="${last_tag}.post$(date -u +%y%m%d%H%M)"
     48 		update_version "setup.py" "s/VERSION =.+/VERSION = '$version_replace'/"
     49 		update_version "python2/httplib2/__init__.py" "s/__version__ =.+/__version__ = '$version_replace'/"
     50 		update_version "python3/httplib2/__init__.py" "s/__version__ =.+/__version__ = '$version_replace'/"
     51 		version_check "$version_replace"
     52 	fi
     53 }
     54 
     55 interactive() {
     56 	echo 'script/release: interactive mode for creating new tagged releases with human assistance' >&2
     57 
     58 	local branch="${1-$(git symbolic-ref --short HEAD)}"
     59 	version="$(PYTHONPATH=$PWD/python3 python3 -c 'import httplib2; print(httplib2.__version__)')"
     60 	printf "\nbranch: %s httplib2.__version__: '%s'\n" $branch $version >&2
     61 
     62 	if [[ "$branch" != "master" ]] ; then
     63 		echo "Must be on master" >&2
     64 		exit 1
     65 	fi
     66 	assert_tree_clean
     67 
     68 	last_commit_message=$(git show --format="%s" --no-patch HEAD)
     69 	expect_commit_message="v$version release"
     70 	if [[ "$last_commit_message" != "$expect_commit_message" ]] ; then
     71 		printf "Last commit message: '%s' expected: '%s'\n" "$last_commit_message" "$expect_commit_message" >&2
     72 		if confirm "Create release commit? [yN] " ; then
     73 			create_commit
     74 		elif ! confirm "Continue without proper release commit? [yN] " ; then
     75 			exit 1
     76 		fi
     77 	fi
     78 	confirm "Continue? [yN] " || exit 1
     79 
     80 	echo "Creating tag v$version" >&2
     81 	if ! git tag "v$version" ; then
     82 		echo "git tag failed " >&2
     83 		confirm "Continue still? [yN] " || exit 1
     84 	fi
     85 
     86     echo "Building package" >&2
     87     find . -name '*.pyc' -o -name '*.pyo' -o -name '*.orig' -delete
     88     rm -rf python{2,3}/.cache
     89     rm -rf build dist
     90     # TODO: sdist bdist_wheel
     91     # but wheels don't roll well with our 2/3 split code base
     92     local venv=./venv-release
     93     if [[ ! -d "$venv" ]] ; then
     94         virtualenv $venv
     95         $venv/bin/pip install -U pip setuptools wheel twine
     96     fi
     97     $venv/bin/python setup.py sdist
     98 
     99 	if confirm "Upload to PyPI? Use in special situation, normally CI (Travis) will upload to PyPI. [yN] " ; then
    100 		$venv/bin/twine upload dist/* || exit 1
    101 	fi
    102 
    103 	git push --tags
    104 }
    105 
    106 create_commit() {
    107 	echo "" >&2
    108 	echo "Plan:" >&2
    109 	echo "1. bump version" >&2
    110 	echo "2. update CHANGELOG" >&2
    111 	echo "3. commit" >&2
    112 	echo "4. run bin/release again" >&2
    113 	echo "" >&2
    114 
    115 	bump_version
    116 	edit_news
    117 
    118 	git diff
    119 	confirm "Ready to commit? [Yn] " || exit 1
    120 	git commit -a -m "v$version_next release"
    121 
    122 	echo "Re-exec $0 to continue" >&2
    123 	exec $0
    124 }
    125 
    126 bump_version() {
    127 	local current=$version
    128 	echo "Current version: '$current'" >&2
    129 	echo -n "Enter next version (empty to abort): " >&2
    130 	read version_next
    131 	if [[ -z "$version_next" ]] ; then
    132 		exit 1
    133 	fi
    134 	echo "Next version:    '$version_next'" >&2
    135 
    136 	update_version "python3/httplib2/__init__.py" "s/__version__ =.+/__version__ = '$version_next'/"
    137 	update_version "python2/httplib2/__init__.py" "s/__version__ =.+/__version__ = '$version_next'/"
    138 	update_version "setup.py" "s/VERSION =.+/VERSION = '$version_next'/"
    139 
    140 	confirm "Confirm changes? [yN] " || exit 1
    141 }
    142 
    143 update_version() {
    144 	local path="$1"
    145 	local sed_expr="$2"
    146 		# sed -E --in-place='' -e "s/VERSION =.+/VERSION = '$version_replace'/" setup.py
    147 		# sed -E --in-place='' -e "s/__version__ =.+/__version__ = '$version_replace'/" python2/httplib2/__init__.py python3/httplib2/__init__.py
    148 	echo "Updating file '$path'" >&2
    149 	if ! sed -E --in-place='' -e "$sed_expr" "$path" ; then
    150 		echo "sed error $?" >&2
    151 		exit 1
    152 	fi
    153 	assert_modified "$path"
    154 	echo "" >&2
    155 }
    156 
    157 edit_news() {
    158 	echo "Changes since last release:" >&2
    159 	git log --format='%h   %an   %s' "v$version"^.. -- || exit 1
    160 	echo "" >&2
    161 
    162 	patch -p1 <<EOT
    163 diff a/CHANGELOG b/CHANGELOG
    164 --- a/CHANGELOG
    165 +++ b/CHANGELOG
    166 @@ -0,0 +1,4 @@
    167 +$version_next
    168 +
    169 +  EDIT HERE. Describe important changes and link to more information.
    170 +
    171 EOT
    172 
    173 	local editor=$(which edit 2>/dev/null)
    174 	[[ -z "$editor" ]] && editor="$EDITOR"
    175 	if [[ -n "$editor" ]] ; then
    176 		if confirm "Open default editor for CHANGELOG? [Yn] " ; then
    177 			$editor CHANGELOG
    178 		else
    179 			confirm "Edit CHANGELOG manually and press any key"
    180 		fi
    181 	else
    182 		echo "Unable to determine default text editor." >&2
    183 		confirm "Edit CHANGELOG manually and press any key"
    184 	fi
    185 	echo "" >&2
    186 
    187 	assert_modified CHANGELOG
    188 
    189 	echo "" >&2
    190 	confirm "Confirm changes? [yN] " || exit 1
    191 }
    192 
    193 assert_modified() {
    194 	local path="$1"
    195 	if git diff --exit-code "$path" ; then
    196 		echo "File '$path' is not modified" >&2
    197 		exit 1
    198 	fi
    199 }
    200 
    201 assert_tree_clean() {
    202 	if [[ -n "$(git status --short -uall)" ]] ; then
    203 		echo "Tree must be clean. git status:" >&2
    204 		echo "" >&2
    205 		git status --short -uall
    206 		echo "" >&2
    207 		exit 1
    208 	fi
    209 }
    210 
    211 version_check() {
    212 	local need=$1
    213 	local version_setup=$(fgrep 'VERSION =' setup.py |tr -d " '" |cut -d\= -f2)
    214 	local version_py2=$(cd python2 ; python2 -Es -c 'import httplib2;print(httplib2.__version__)')
    215 	local version_py3=$(cd python3 ; python3 -Es -c 'import httplib2;print(httplib2.__version__)')
    216 	if [[ "$version_setup" != "$need" ]] ; then
    217 		echo "error: setup.py VERSION=$version_setup expected=$need" >&1
    218 		exit 1
    219 	fi
    220 	if [[ "$version_py2" != "$need" ]] ; then
    221 		echo "error: python2/httplib2/__init__.py:__version__=$version_py2 expected=$need" >&1
    222 		exit 1
    223 	fi
    224 	if [[ "$version_py3" != "$need" ]] ; then
    225 		echo "error: python3/httplib2/__init__.py:__version__=$version_py3 expected=$need" >&1
    226 		exit 1
    227 	fi
    228 }
    229 
    230 confirm() {
    231 	local reply
    232 	local prompt="$1"
    233 	read -n1 -p "$prompt" reply >&2
    234 	echo "" >&2
    235 	rc=0
    236 	local default_y=" \[Yn\] $"
    237 	if [[ -z "$reply" ]] && [[ "$prompt" =~ $default_y ]] ; then
    238 		reply="y"
    239 	fi
    240 	[[ "$reply" != "y" ]] && rc=1
    241 	return $rc
    242 }
    243 
    244 main "$@"
    245