forked from theupdateframework/specification
-
Notifications
You must be signed in to change notification settings - Fork 0
/
check_release.py
160 lines (124 loc) · 5.3 KB
/
check_release.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
"""
<Program Name>
check_release.py
<Author>
Lukas Puehringer <[email protected]>
<Started>
Jan 3, 2020
<Copyright>
See LICENSE for licensing information.
<Purpose>
Check that specification updates are performed according to the versioning
requirements in README.rst.
Expects GitHub Actions environment variables:
- GITHUB_REF the ref that triggered the workflow (i.e refs/pull/33/merge)
- GITHUB_BASE_REF the target branch (usually master)
- GITHUB_HEAD_REF the name of the submitters branch
(see https://docs.github.com/en/free-pro-team@latest/actions/reference/environment-variables)
"""
import os
import re
import sys
import shlex
import datetime
import subprocess
SPEC_NAME = "tuf-spec.md"
LAST_MODIFIED_PATTERN = "Date: %Y-%m-%d\n"
LAST_MODIFIED_LINENO = 6
VERSION_PATTERN = r"^Text Macro: VERSION (\d*)\.(\d*)\.(\d*)$"
VERSION_LINENO = 19
class SpecError(Exception):
"""Common error message part. """
def __init__(self, msg):
super().__init__(
msg + ", please see 'Versioning' section in README.rst for details.")
def get_spec_head():
"""Return the lines (as list) at the head of the file that contain last date
modified and version. """
with open(SPEC_NAME) as spec_file:
spec_head = [next(spec_file)
for x in range(max(VERSION_LINENO, LAST_MODIFIED_LINENO))]
return spec_head
def get_date(spec_head):
"""Parse date from spec head and return datetime object. """
last_modified_line = spec_head[LAST_MODIFIED_LINENO - 1]
try:
date = datetime.datetime.strptime(last_modified_line,
LAST_MODIFIED_PATTERN)
except ValueError as e:
raise SpecError("expected to match '{}' (datetime format) in line {}, but"
" got '{}': {}.".format(LAST_MODIFIED_PATTERN, LAST_MODIFIED_LINENO,
last_modified_line, e))
return date
def get_version(spec_head):
"""Parse version from spec head and return (major, minor, patch) tuple. """
version_line = spec_head[VERSION_LINENO - 1]
version_match = re.search(VERSION_PATTERN, version_line)
if not version_match:
raise SpecError("expected to match '{}' (regex) in line {}, but got '{}'."
.format(VERSION_PATTERN, VERSION_LINENO, version_line))
major, minor, patch = version_match.groups()
return int(major), int(minor), int(patch)
def main():
"""Check that the current branch is based off of the master branch and that
the last modified date and version number in the specification document
header are higher than in the master branch, i.e. were bumped. """
# Check that the current branch is based off of the master branch
current_branch = os.environ["GITHUB_REF"].lstrip("refs/")
try:
subprocess.run(
shlex.split("git merge-base --is-ancestor origin/master {}".format(
current_branch)), check=True)
except subprocess.CalledProcessError as e:
raise SpecError("make sure the current branch is based off of master")
# Only proceed if the spec itself was changed
git_run = subprocess.run(shlex.split(
"git diff --name-only origin/master {}".format(current_branch)),
capture_output=True, check=True, text=True)
modified_files = git_run.stdout.split() or []
if SPEC_NAME not in modified_files:
print("*"*68)
print("{} not modified, skipping version and date check.".format(SPEC_NAME))
print("*"*68)
return
# Read the first few lines from the updated specification document and
# extract date and version from spec file header in the current branch
spec_head = get_spec_head()
date_new = get_date(spec_head)
version_new = get_version(spec_head)
# Checkout master branch
subprocess.run(shlex.split("git checkout -q master"), check=True)
# Read the first few lines from the previous specification document and
# extract date and version from spec file header in the master branch
spec_head = get_spec_head()
date_prev = get_date(spec_head)
version_prev = get_version(spec_head)
# Assert date update
if not (date_new > date_prev or
date_new.date() == date_prev.date() == datetime.date.today()):
raise SpecError("new 'last modified date' ({:%d %B %Y}) must be greater"
" than the previous one ({:%d %B %Y}) or both must be today.".format(
date_new, date_prev))
# Assert version bump type depending on the PR originating branch
# - if the originating branch is 'draft', it must be a major (x)or minor bump
# - otherwise, it must be a patch bump
if os.environ["GITHUB_BASE_REF"] == "draft":
if not (((version_new[0] > version_prev[0]) !=
(version_new[1] > version_prev[1])) and
(version_new[2] == version_prev[2])):
raise SpecError("new version ({}) must have exactly one of a greater"
" major or a greater minor version number than the previous one ({}),"
" if the PR originates from the 'draft' branch.".format(version_new,
version_prev))
else:
if not (version_new[:2] == version_prev[:2] and
version_new[2] > version_prev[2]):
raise SpecError("new version ({}) must have exactly a greater patch"
" version number than the previous one ({}), if the PR originates"
" from a feature branch (i.e. not 'draft')".format(version_new,
version_prev))
print("*"*68)
print("thanks for correctly bumping version and last modified date. :)")
print("*"*68)
if __name__ == '__main__':
main()