Skip to content

Commit 130dc7a

Browse files
authored
Merge pull request #87 from lukpueh/spec-versioning
Add specification versioning- and release management instructions and checks
2 parents 0f56aee + 8c2af51 commit 130dc7a

File tree

3 files changed

+208
-2
lines changed

3 files changed

+208
-2
lines changed

.travis.yml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
language: python
2+
3+
# Disable auto-cloning and ...
4+
git:
5+
clone: false
6+
7+
# ... instead manually fetch and checkout the pull request source branch, which
8+
# is expected by check_release.py
9+
# https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/checking-out-pull-requests-locally
10+
install:
11+
- git clone --depth=50 https://github.com/${TRAVIS_REPO_SLUG}.git ${TRAVIS_REPO_SLUG}
12+
- cd ${TRAVIS_REPO_SLUG}
13+
- git fetch origin pull/${TRAVIS_PULL_REQUEST}/head:${TRAVIS_PULL_REQUEST_BRANCH}
14+
- git checkout -qf ${TRAVIS_PULL_REQUEST_BRANCH}
15+
16+
script:
17+
- python check_release.py

README.rst

+31-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
The Update Framework specification
22
----------------------------------
33

4-
Latest: `version 1.0.0 <https://github.com/theupdateframework/specification/blob/master/tuf-spec.md>`_
4+
- `latest stable <https://github.com/theupdateframework/specification/blob/master/tuf-spec.md>`_
5+
- `current draft <https://github.com/theupdateframework/specification/blob/draft/tuf-spec.md>`_
6+
- `new changes since latest stable <https://github.com/theupdateframework/specification/compare/master..draft>`_
7+
- `release history <https://github.com/theupdateframework/specification/tags>`_
58

69

710
Contact
@@ -31,7 +34,33 @@ Versioning
3134
----------
3235

3336
The TUF specification uses `Semantic Versioning 2.0.0 <https://semver.org/>`_
34-
for its version numbers.
37+
(semver) for its version numbers, and a gitflow-based release management:
38+
39+
- The 'master' branch of this repository always points to the latest stable
40+
version of the specification.
41+
- The 'draft' branch of this repository always points to the latest development
42+
version of the specification and must always be based off of the latest
43+
'master' branch.
44+
- Contributors must submit changes as pull requests against these branches,
45+
depending on the type of the change (see semver rules).
46+
- For patch-type changes, pull requests may be submitted directly against the
47+
'master' branch.
48+
- For major- and minor-type changes, pull requests must be submitted against
49+
the 'draft' branch.
50+
- Maintainers may, from time to time, decide that the 'draft' branch is ready
51+
for a new major or minor release, and submit a pull request from 'draft'
52+
against 'master'.
53+
- Before merging a branch with 'master' the 'last modified date' and 'version'
54+
in the specification header must be bumped.
55+
- Merges with 'master' that originate from the 'draft' branch must bump either
56+
the major or minor version number.
57+
- Merges with 'master' that originate from any other branch must bump the patch
58+
version number.
59+
- Merges with 'master' must be followed by a git tag for the new version
60+
number.
61+
- Merges with 'master' must be followed by a rebase of 'draft' onto 'master'.
62+
63+
3564

3665
Acknowledgements
3766
----------------

check_release.py

+160
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 <[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

Comments
 (0)