Skip to content

Commit 1b83ca7

Browse files
committed
Update tag.py to support release candidate versions
1 parent e83ae55 commit 1b83ca7

File tree

1 file changed

+48
-22
lines changed

1 file changed

+48
-22
lines changed

tag.py

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@
1313
import sys
1414
import re
1515

16-
SEMVER_REGEX = re.compile(fr'^v(?P<major>\d+?).(?P<minor>\d+?).(?P<patch>\d+?)$')
16+
SEMVER_REGEX = re.compile(fr'^v(?P<major>\d+?).(?P<minor>\d+?).(?P<patch>\d+?)(rc(?P<rc>\d+?))?$')
1717
react_api_env_var_name = 'REACT_APP_API_VERSION'
1818
REACT_API_ENV_REGEX = re.compile(fr'{react_api_env_var_name}=(?P<version>.*)')
1919
semver_options = {
2020
'major': 'Makes incompatible API changes',
2121
'minor': 'Adds (backwards-compatible) functionality',
2222
'patch': 'Makes (backwards-compatible) bug fixes',
23+
'rc': 'Increments on an existing RC tag',
2324
'none': 'Don\'t update this service'
2425
}
2526
_default_subprocess_options = {'shell': True, 'check': True, 'capture_output': True, 'text': True}
@@ -29,27 +30,22 @@
2930
'api': dict({'cwd': api_working_directory}, **_default_subprocess_options),
3031
}
3132

32-
def assert_using_a_tty():
33-
if not sys.stdout.isatty():
34-
print("Error: Must run this method with a tty. If you're using windows try:\n" + f"winpty {' '.join(sys.argv)}")
35-
sys.exit(1)
36-
3733
def parse_command_line_arguments():
3834
parser = argparse.ArgumentParser(description='Tag the code ready for a release.\n' +
39-
'; '.join(option.upper() + ': ' + description for option, description in semver_options.items()) + ".")
40-
parser.add_argument('--app', choices=['major', 'minor', 'patch'], help='Set the semver update type for the App')
35+
'; '.join(option.upper() + ': ' + description for option, description in semver_options.items()) + ".")
36+
parser.add_argument('--app', choices=['major', 'minor', 'patch', 'rc'], help='Set the semver update type for the App')
4137
parser.add_argument('--api', choices=semver_options.keys(), help='Set the semver update type for the API')
4238
return parser.parse_args()
4339

4440
def ask_for_update_type_for(service_name):
4541
while True:
46-
user_response = input(f'Are the changes to the {service_name}: [0] NONE, [1] PATCH, [2] MINOR, or [3] MAJOR?\n')
47-
if len(user_response) != 1 and user_response not in ('0', '1', '2', '3'):
48-
print("Please respond either '0', '1', '2' or '3'")
42+
user_response = input(f'Are the changes to the {service_name}: [0] NONE, [R] RELEASE CANDIDATE (RC), [1] PATCH, [2] MINOR, or [3] MAJOR?\n')
43+
if len(user_response) != 1 and user_response not in ('0', 'R', '1', '2', '3'):
44+
print("Please respond either '0', 'R', '1', '2' or '3'")
4945
elif service_name == 'app' and user_response == '0':
5046
print("Our release procedure does not allow a new API to be released without an App update.")
5147
else:
52-
return {'0': 'none', '1': 'patch', '2': 'minor', '3': 'major'}[user_response]
48+
return {'0': 'none', 'R': 'rc', '1': 'patch', '2': 'minor', '3': 'major'}[user_response]
5349

5450
def get_update_description_from_user(cli_input):
5551
# try to retrieve from command line args
@@ -61,9 +57,22 @@ def get_update_description_from_user(cli_input):
6157
return update_description
6258

6359
def get_versions_from_github():
60+
app_mainline = [x['name'] for x in requests.get('https://api.github.com/repos/isaacphysics/isaac-react-app/tags').json() if 'rc' not in x['name']][0]
61+
api_mainline = [x['name'] for x in requests.get('https://api.github.com/repos/isaacphysics/isaac-api/tags').json() if 'rc' not in x['name']][0]
62+
try:
63+
app_rc = [x['name'] for x in requests.get('https://api.github.com/repos/isaacphysics/isaac-react-app/tags').json() if 'rc' in x['name']][0]
64+
except IndexError:
65+
app_rc = None
66+
try:
67+
api_rc = [x['name'] for x in requests.get('https://api.github.com/repos/isaacphysics/isaac-api/tags').json() if 'rc' in x['name']][0]
68+
except IndexError:
69+
api_rc = None
70+
6471
return {
65-
'app': requests.get('https://api.github.com/repos/isaacphysics/isaac-react-app/tags').json()[0]['name'],
66-
'api': requests.get('https://api.github.com/repos/isaacphysics/isaac-api/tags').json()[0]['name'],
72+
'app-mainline': app_mainline,
73+
'api-mainline': api_mainline,
74+
'app-rc': app_rc,
75+
'api-rc': api_rc
6776
}
6877

6978
def get_build_results_from_github(repo, branch):
@@ -85,18 +94,36 @@ def get_build_results_from_github(repo, branch):
8594
def increment_version(update_type):
8695
def repl_matcher(match):
8796
if update_type != 'none':
88-
version = {'major': int(match.group('major')), 'minor': int(match.group('minor')), 'patch': int(match.group('patch'))}
89-
version_order = ['major', 'minor', 'patch']
97+
version = {'major': int(match.group('major')), 'minor': int(match.group('minor')), 'patch': int(match.group('patch')), 'rc': None if not match.group('rc') else int(match.group('rc'))}
98+
version_order = ['major', 'minor', 'patch', 'rc']
9099
for ver in version_order:
91100
if version_order.index(update_type) < version_order.index(ver):
92101
version[ver] = 0
93102
elif ver == update_type:
94103
version[ver] += 1
95-
return f"v{version['major']}.{version['minor']}.{version['patch']}"
104+
if update_type == 'rc':
105+
return f"v{version['major']}.{version['minor']}.{version['patch']}rc{version['rc']}"
106+
else:
107+
return f"v{version['major']}.{version['minor']}.{version['patch']}"
96108
else:
97109
return match.group(0)
98110
return repl_matcher
99111

112+
def get_previous_versions_for_update_type(previous_versions, update_description):
113+
prev = {}
114+
for service_name in ['app', 'api']:
115+
service_update_description = update_description[service_name]
116+
if service_update_description == 'rc':
117+
if previous_versions[f'{service_name}-rc']:
118+
prev[service_name] = previous_versions[f"{service_name}-rc"]
119+
else:
120+
print("Error: RC update type requires an existing release candidate tag to increment on, and none was "
121+
"found. You may need to create an initial tag (e.g. vX.Y.Zrc0) by hand.")
122+
sys.exit(1)
123+
else:
124+
prev[service_name] = previous_versions[f'{service_name}-mainline']
125+
return prev
126+
100127
def update_versions(previous_versions, update_description, snapshot=False):
101128
update_versions = {}
102129
for service_name in ['app', 'api']:
@@ -149,7 +176,7 @@ def set_versions(versions, update_description):
149176
# Record the App version
150177
# package.json
151178
subprocess.run(f"npm --no-git-tag-version version {versions['app']}", **subprocess_options['app'])
152-
179+
153180
# Record the API version
154181
if update_description['api'] != 'none':
155182
# .env
@@ -182,21 +209,20 @@ def commit_and_push_changes(versions, update_description):
182209

183210

184211
if __name__ == '__main__':
185-
assert_using_a_tty()
186-
187212
cli_args = parse_command_line_arguments()
188213
update_description = get_update_description_from_user(cli_args)
189214

190215
check_app_and_api_are_clean(update_description)
191216

192217
most_recent_versions = get_versions_from_github()
193-
target_versions = update_versions(most_recent_versions, update_description)
218+
relevant_recent_versions = get_previous_versions_for_update_type(most_recent_versions, update_description)
219+
target_versions = update_versions(relevant_recent_versions, update_description)
194220
check_user_is_ready_to_release(target_versions, update_description)
195221

196222
set_versions(target_versions, update_description)
197223
commit_and_tag_changes(target_versions, update_description)
198224

199-
bump_update_description = {service: 'patch' if update != 'none' else 'none' for service, update in update_description.items()}
225+
bump_update_description = {service: update if update in ['rc', 'none'] else 'patch' for service, update in update_description.items()}
200226
bumped_versions = update_versions(target_versions, bump_update_description, snapshot=True)
201227
set_versions(bumped_versions, update_description)
202228
commit_and_push_changes(target_versions, update_description)

0 commit comments

Comments
 (0)