aboutsummaryrefslogtreecommitdiff
path: root/script/release
blob: a2ff80d0a9dbd49a0857c047a38d577f38135ee3 (plain)
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
	local venv=./venv-release
	if [[ ! -d "$venv" ]] ; then
		virtualenv $venv
		$venv/bin/pip install -U check-manifest pip 'setuptools>=43.0' wheel twine
	fi
	$venv/bin/python setup.py clean --all
	$venv/bin/python setup.py sdist bdist_wheel
	$venv/bin/check-manifest || echo "FIXME check-manifest" >&2

	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 "$@"