1
+ """
2
+ <Program Name>
3
+ check_release.py
4
+
5
+ <Author>
6
+ Lukas Puehringer <[email protected] >
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 -q 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