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 local venv=./venv-release 91 if [[ ! -d "$venv" ]] ; then 92 virtualenv $venv 93 $venv/bin/pip install -U pip setuptools wheel twine 94 fi 95 $venv/bin/python setup.py clean --all 96 $venv/bin/python setup.py sdist bdist_wheel 97 98 if confirm "Upload to PyPI? Use in special situation, normally CI (Travis) will upload to PyPI. [yN] " ; then 99 $venv/bin/twine upload dist/* || exit 1 100 fi 101 102 git push --tags 103} 104 105create_commit() { 106 echo "" >&2 107 echo "Plan:" >&2 108 echo "1. bump version" >&2 109 echo "2. update CHANGELOG" >&2 110 echo "3. commit" >&2 111 echo "4. run bin/release again" >&2 112 echo "" >&2 113 114 bump_version 115 edit_news 116 117 git diff 118 confirm "Ready to commit? [Yn] " || exit 1 119 git commit -a -m "v$version_next release" 120 121 echo "Re-exec $0 to continue" >&2 122 exec $0 123} 124 125bump_version() { 126 local current=$version 127 echo "Current version: '$current'" >&2 128 echo -n "Enter next version (empty to abort): " >&2 129 read version_next 130 if [[ -z "$version_next" ]] ; then 131 exit 1 132 fi 133 echo "Next version: '$version_next'" >&2 134 135 update_version "python3/httplib2/__init__.py" "s/__version__ =.+/__version__ = '$version_next'/" 136 update_version "python2/httplib2/__init__.py" "s/__version__ =.+/__version__ = '$version_next'/" 137 update_version "setup.py" "s/VERSION =.+/VERSION = '$version_next'/" 138 139 confirm "Confirm changes? [yN] " || exit 1 140} 141 142update_version() { 143 local path="$1" 144 local sed_expr="$2" 145 # sed -E --in-place='' -e "s/VERSION =.+/VERSION = '$version_replace'/" setup.py 146 # sed -E --in-place='' -e "s/__version__ =.+/__version__ = '$version_replace'/" python2/httplib2/__init__.py python3/httplib2/__init__.py 147 echo "Updating file '$path'" >&2 148 if ! sed -E --in-place='' -e "$sed_expr" "$path" ; then 149 echo "sed error $?" >&2 150 exit 1 151 fi 152 assert_modified "$path" 153 echo "" >&2 154} 155 156edit_news() { 157 echo "Changes since last release:" >&2 158 git log --format='%h %an %s' "v$version"^.. -- || exit 1 159 echo "" >&2 160 161 patch -p1 <<EOT 162diff a/CHANGELOG b/CHANGELOG 163--- a/CHANGELOG 164+++ b/CHANGELOG 165@@ -0,0 +1,4 @@ 166+$version_next 167+ 168+ EDIT HERE. Describe important changes and link to more information. 169+ 170EOT 171 172 local editor=$(which edit 2>/dev/null) 173 [[ -z "$editor" ]] && editor="$EDITOR" 174 if [[ -n "$editor" ]] ; then 175 if confirm "Open default editor for CHANGELOG? [Yn] " ; then 176 $editor CHANGELOG 177 else 178 confirm "Edit CHANGELOG manually and press any key" 179 fi 180 else 181 echo "Unable to determine default text editor." >&2 182 confirm "Edit CHANGELOG manually and press any key" 183 fi 184 echo "" >&2 185 186 assert_modified CHANGELOG 187 188 echo "" >&2 189 confirm "Confirm changes? [yN] " || exit 1 190} 191 192assert_modified() { 193 local path="$1" 194 if git diff --exit-code "$path" ; then 195 echo "File '$path' is not modified" >&2 196 exit 1 197 fi 198} 199 200assert_tree_clean() { 201 if [[ -n "$(git status --short -uall)" ]] ; then 202 echo "Tree must be clean. git status:" >&2 203 echo "" >&2 204 git status --short -uall 205 echo "" >&2 206 exit 1 207 fi 208} 209 210version_check() { 211 local need=$1 212 local version_setup=$(fgrep 'VERSION =' setup.py |tr -d " '" |cut -d\= -f2) 213 local version_py2=$(cd python2 ; python2 -Es -c 'import httplib2;print(httplib2.__version__)') 214 local version_py3=$(cd python3 ; python3 -Es -c 'import httplib2;print(httplib2.__version__)') 215 if [[ "$version_setup" != "$need" ]] ; then 216 echo "error: setup.py VERSION=$version_setup expected=$need" >&1 217 exit 1 218 fi 219 if [[ "$version_py2" != "$need" ]] ; then 220 echo "error: python2/httplib2/__init__.py:__version__=$version_py2 expected=$need" >&1 221 exit 1 222 fi 223 if [[ "$version_py3" != "$need" ]] ; then 224 echo "error: python3/httplib2/__init__.py:__version__=$version_py3 expected=$need" >&1 225 exit 1 226 fi 227} 228 229confirm() { 230 local reply 231 local prompt="$1" 232 read -n1 -p "$prompt" reply >&2 233 echo "" >&2 234 rc=0 235 local default_y=" \[Yn\] $" 236 if [[ -z "$reply" ]] && [[ "$prompt" =~ $default_y ]] ; then 237 reply="y" 238 fi 239 [[ "$reply" != "y" ]] && rc=1 240 return $rc 241} 242 243main "$@" 244