ronnie
2022-10-14 1504bb53e29d3d46222c0b3ea994fc494b48e153
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
#!/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 <<EOT
diff a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -0,0 +1,4 @@
+$version_next
+
+  EDIT HERE. Describe important changes and link to more information.
+
EOT
 
   local editor=$(which edit 2>/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 "$@"