forked from oppia/oppia
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cut_release_branch.py
192 lines (153 loc) · 6.68 KB
/
cut_release_branch.py
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
# Copyright 2017 The Oppia Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Helper script used for creating a new release branch on GitHub.
ONLY RELEASE COORDINATORS SHOULD USE THIS SCRIPT.
Usage: Run this script from your oppia root folder:
python scripts/cut_release_branch.py --version="x.y.z"
where x.y.z is the new version of Oppia, e.g. 2.5.3.
"""
import argparse
import json
import re
import subprocess
import sys
import urllib
import common # pylint: disable=relative-import
def new_version_type(arg, pattern=re.compile(r'\d\.\d\.\d')):
"""Checks that the new version name matches the expected pattern.
Args:
arg: str. The new version name.
pattern: RegularExpression. The pattern that release version should
match.
Raises:
argparse.ArgumentTypeError: The new version name does not match
the pattern.
Returns:
str. The new version name with correct pattern.
"""
if not pattern.match(arg):
raise argparse.ArgumentTypeError(
'The format of "new_version" should be: x.x.x')
return arg
_PARSER = argparse.ArgumentParser()
_PARSER.add_argument(
'--new_version', help='new version to be released', type=new_version_type)
PARSED_ARGS = _PARSER.parse_args()
if PARSED_ARGS.new_version:
TARGET_VERSION = PARSED_ARGS.new_version
else:
raise Exception('ERROR: A "new_version" arg must be specified.')
# Construct the new branch name.
NEW_BRANCH_NAME = 'release-%s' % TARGET_VERSION
def _verify_target_branch_does_not_already_exist(remote_alias):
"""Checks that the new release branch doesn't already exist locally or
remotely.
Args:
remote_alias: str. The alias that points to the remote oppia
repository. Example: When calling git remote -v, you get:
upstream https://github.com/oppia/oppia.git (fetch),
where 'upstream' is the alias that points to the remote oppia
repository.
Raises:
Exception: The target branch name already exists locally.
Exception: The target branch name already exists on the remote
oppia repository.
"""
git_branch_output = subprocess.check_output(['git', 'branch'])
if NEW_BRANCH_NAME in git_branch_output:
raise Exception(
'ERROR: The target branch name already exists locally. '
'Run "git branch -D %s" to delete it.' % NEW_BRANCH_NAME)
git_ls_remote_output = subprocess.check_output(
['git', 'ls-remote', '--heads', remote_alias])
remote_branch_ref = 'refs/heads/%s' % NEW_BRANCH_NAME
if remote_branch_ref in git_ls_remote_output:
raise Exception(
'ERROR: The target branch name already exists on the remote repo.')
def _verify_target_version_is_consistent_with_latest_released_version():
"""Checks that the target version is consistent with the latest released
version on GitHub.
Raises:
Exception: Failed to fetch latest release info from GitHub.
Exception: Could not parse version number of latest GitHub release.
AssertionError: The previous and the current major version are not the
same.
AssertionError: The current patch version is not equal to previous patch
version plus one.
AssertionError: The current patch version is greater or equal to 10.
AssertionError: The current minor version is not equal to previous
minor version plus one.
AssertionError: The current patch version is different than 0.
"""
response = urllib.urlopen(
'https://api.github.com/repos/oppia/oppia/releases/latest')
if response.getcode() != 200:
raise Exception(
'ERROR: Failed to fetch latest release info from GitHub')
data = json.load(response)
latest_release_tag_name = data['tag_name']
match_result = re.match(r'v(\d)\.(\d)\.(\d)', latest_release_tag_name)
if match_result is None:
raise Exception(
'ERROR: Could not parse version number of latest GitHub release.')
prev_major, prev_minor, prev_patch = match_result.group(1, 2, 3)
match_result = re.match(r'(\d)\.(\d)\.(\d)', TARGET_VERSION)
curr_major, curr_minor, curr_patch = match_result.group(1, 2, 3)
# This will need to be overridden if the major version changes.
assert prev_major == curr_major, 'Unexpected major version change.'
if prev_minor == curr_minor:
assert int(curr_patch) == int(prev_patch) + 1
assert int(curr_patch) < 10
else:
assert int(curr_minor) == int(prev_minor) + 1
assert int(curr_patch) == 0
def _execute_branch_cut():
"""Pushes the new release branch to Github."""
# Do prerequisite checks.
common.require_cwd_to_be_oppia()
common.verify_local_repo_is_clean()
common.verify_current_branch_name('develop')
# Update the local repo.
remote_alias = common.get_remote_alias('git@github.com:oppia/oppia.git')
subprocess.call(['git', 'pull', remote_alias])
_verify_target_branch_does_not_already_exist(remote_alias)
_verify_target_version_is_consistent_with_latest_released_version()
# The release coordinator should verify that tests are passing on develop
# before checking out the release branch.
common.open_new_tab_in_browser_if_possible(
'https://github.com/oppia/oppia#oppia---')
while True:
print (
'Please confirm: are Travis checks passing on develop? (y/n) ')
answer = raw_input().lower()
if answer in ['y', 'ye', 'yes']:
break
elif answer:
print (
'Tests should pass on develop before this script is run. '
'Exiting.')
sys.exit()
# Cut a new release branch.
print 'Cutting a new release branch: %s' % NEW_BRANCH_NAME
subprocess.call(['git', 'checkout', '-b', NEW_BRANCH_NAME])
# Push the new release branch to GitHub.
print 'Pushing new release branch to GitHub.'
subprocess.call(['git', 'push', remote_alias, NEW_BRANCH_NAME])
print ''
print (
'New release branch successfully cut. You are now on branch %s' %
NEW_BRANCH_NAME)
print 'Done!'
if __name__ == '__main__':
_execute_branch_cut()