Skip to content

Commit 5bf5626

Browse files
committed
Add check release script
Add script to run on Travis upon a GitHub pull request, that checks that versioning- and release management rules are followed.
1 parent aa9e94c commit 5bf5626

File tree

1 file changed

+160
-0
lines changed

1 file changed

+160
-0
lines changed

check_release.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
"""
2+
<Program Name>
3+
check_release.py
4+
5+
<Author>
6+
Lukas Puehringer <lukas.puehringer@nyu.edu>
7+
8+
<Started>
9+
Jan 3, 2020
10+
11+
<Copyright>
12+
See LICENSE for licensing information.
13+
14+
<Purpose>
15+
Check that specification updates are performed according to the versioning
16+
requirements in README.rst.
17+
18+
Expects Travis environment variables:
19+
- TRAVIS_BRANCH
20+
- TRAVIS_PULL_REQUEST_BRANCH
21+
(see https://docs.travis-ci.com/user/environment-variables/)
22+
23+
"""
24+
import os
25+
import re
26+
import sys
27+
import shlex
28+
import datetime
29+
import subprocess
30+
31+
SPEC_NAME = "tuf-spec.md"
32+
33+
LAST_MODIFIED_PATTERN = "Last modified: **%d %B %Y**\n"
34+
LAST_MODIFIED_LINENO = 3
35+
36+
VERSION_PATTERN = r"^Version: \*\*(\d*)\.(\d*)\.(\d*)\*\*$"
37+
VERSION_LINENO = 5
38+
39+
class SpecError(Exception):
40+
"""Common error message part. """
41+
def __init__(self, msg):
42+
super().__init__(
43+
msg + " please see 'Versioning' section in README.rst for details.")
44+
45+
46+
def get_spec_head():
47+
"""Return the lines (as list) at the head of the file that contain last date
48+
modified and version. """
49+
with open(SPEC_NAME) as spec_file:
50+
spec_head = [next(spec_file)
51+
for x in range(max(VERSION_LINENO, LAST_MODIFIED_LINENO))]
52+
53+
return spec_head
54+
55+
56+
def get_date(spec_head):
57+
"""Parse date from spec head and return datetime object. """
58+
last_modified_line = spec_head[LAST_MODIFIED_LINENO - 1]
59+
60+
try:
61+
date = datetime.datetime.strptime(last_modified_line,
62+
LAST_MODIFIED_PATTERN)
63+
64+
except ValueError as e:
65+
raise SpecError("expected to match '{}' (datetime format) in line {}, but"
66+
" got '{}': {}.".format(LAST_MODIFIED_PATTERN, LAST_MODIFIED_LINENO,
67+
last_modified_line, e))
68+
69+
return date
70+
71+
72+
def get_version(spec_head):
73+
"""Parse version from spec head and return (major, minor, patch) tuple. """
74+
version_line = spec_head[VERSION_LINENO - 1]
75+
76+
version_match = re.search(VERSION_PATTERN, version_line)
77+
if not version_match:
78+
raise SpecError("expected to match '{}' (regex) in line {}, but got '{}'."
79+
.format(VERSION_PATTERN, VERSION_LINENO, version_line))
80+
81+
return version_match.groups()
82+
83+
84+
def main():
85+
"""Check that the current branch is based off of the master branch and that
86+
the last modified date and version number in the specification document
87+
header are higher than in the master branch, i.e. were bumped. """
88+
89+
# Skip version and date comparison on push builds ...
90+
# As per https://docs.travis-ci.com/user/environment-variables/
91+
# if the current job is a push build, this [env] variable is empty ("")
92+
if not os.environ.get("TRAVIS_PULL_REQUEST_BRANCH"):
93+
print("skipping version and date check for non pr builds ...")
94+
sys.exit(0)
95+
96+
# ... also skip on PRs that don't target the master branch
97+
# As per https://docs.travis-ci.com/user/environment-variables/:
98+
# for builds triggered by a pull request this [env variable] is the name of
99+
# the branch targeted by the pull request
100+
if not os.environ.get("TRAVIS_BRANCH") == "master":
101+
print("skipping version and date for builds that don't target master ...")
102+
sys.exit(0)
103+
104+
# Check that the current branch is based off of the master branch
105+
try:
106+
subprocess.run(
107+
shlex.split("git merge-base --is-ancestor master {}".format(
108+
os.environ["TRAVIS_PULL_REQUEST_BRANCH"])), check=True)
109+
110+
except subprocess.CalledProcessError as e:
111+
raise SpecError("make sure the current branch is based off of master")
112+
113+
# Read the first few lines from the updated specification document and
114+
# extract date and version from spec file header in the current branch
115+
spec_head = get_spec_head()
116+
date_new = get_date(spec_head)
117+
version_new = get_version(spec_head)
118+
119+
# Checkout master branch
120+
subprocess.run(shlex.split("git checkout master"), check=True)
121+
122+
# Read the first few lines from the previous specification document and
123+
# extract date and version from spec file header in the master branch
124+
spec_head = get_spec_head()
125+
date_prev = get_date(spec_head)
126+
version_prev = get_version(spec_head)
127+
128+
# Assert date update
129+
if not date_new > date_prev:
130+
raise SpecError("new 'last modified date' ({:%d %B %Y}) must be greater"
131+
" than the previous one ({:%d %B %Y})".format(date_new, date_prev))
132+
133+
# Assert version bump type depending on the PR originating branch
134+
# - if the originating branch is 'draft', it must be a major (x)or minor bump
135+
# - otherwise, it must be a patch bump
136+
if os.environ["TRAVIS_PULL_REQUEST_BRANCH"] == "draft":
137+
if not (((version_new[0] > version_prev[0]) !=
138+
(version_new[1] > version_prev[1])) and
139+
(version_new[2] == version_prev[2])):
140+
raise SpecError("new version ({}) must have exactly one of a greater"
141+
" major or a greater minor version number than the previous one ({}),"
142+
" if the PR originates from the 'draft' branch.".format(version_new,
143+
version_prev))
144+
145+
else:
146+
if not (version_new[:2] == version_prev[:2] and
147+
version_new[2] > version_prev[2]):
148+
raise SpecError("new version ({}) must have exactly a greater patch"
149+
" version number than the previous one ({}), if the PR originates"
150+
" from a feature branch (i.e. not 'draft')".format(version_new,
151+
version_prev))
152+
153+
print("*"*68)
154+
print("thanks for correctly bumping version and last modified date. :)")
155+
print("don't forget to tag the release and to sync 'draft' with master!! :P")
156+
print("*"*68)
157+
158+
159+
if __name__ == '__main__':
160+
main()

0 commit comments

Comments
 (0)