1#!/bin/bash 2set -eu 3cd "$( dirname "${BASH_SOURCE[0]}" )/.." 4 5version= 6version_next= 7 8main() { 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 29auto_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 55interactive() { 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 106create_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 126bump_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 143update_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 157edit_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 163diff 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+ 171EOT 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 193assert_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 201assert_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 211version_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 230confirm() { 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 244main "$@" 245