|
5 | 5 | https://github.com/PyCOMPLETE/PyHEADTAIL/wiki/Our-Development-Workflow |
6 | 6 |
|
7 | 7 | Requires git, hub (the github tool, https://hub.github.com/) and importlib |
8 | | -(included in python >=2.7) installed. |
| 8 | +(included in python >=2.7) to be installed. |
9 | 9 |
|
10 | | -Should be PEP440 conformal. |
| 10 | +Conforms with PEP440 (especially the versioning needs to follow this). |
11 | 11 |
|
12 | 12 | @copyright: CERN |
13 | 13 | @date: 26.01.2017 |
|
18 | 18 | import importlib # available from PyPI for Python <2.7 |
19 | 19 | import os, subprocess |
20 | 20 |
|
| 21 | +# python2/3 compatibility for raw_input/input: |
| 22 | +if hasattr(__builtins__, 'raw_input'): |
| 23 | + input = raw_input |
| 24 | + |
21 | 25 | # CONFIG |
22 | 26 | version_location = 'PyHEADTAIL._version' # in python relative module notation |
23 | 27 | # (e.g. PyHEADTAIL._version for PyHEADTAIL/_version.py) |
24 | 28 | test_script_location = 'prepush' # in python relative module notation |
25 | 29 | release_branch_prefix = 'release/v' # prepended name of release branch |
| 30 | +github_user = 'PyCOMPLETE' |
| 31 | +github_repo = 'PyHEADTAIL' |
26 | 32 |
|
27 | 33 |
|
28 | 34 | parser = argparse.ArgumentParser( |
@@ -125,13 +131,68 @@ def ensure_hub_is_installed(): |
125 | 131 | ''' |
126 | 132 | try: |
127 | 133 | assert subprocess.call(["hub", "--version"]) == 0 |
128 | | - except OSError: |
| 134 | + except (OSError, AssertionError): |
129 | 135 | raise OSError( |
130 | 136 | 'The github command-line tool is needed for ' |
131 | 137 | 'opening the pull request for the release. ' |
132 | 138 | 'Please install hub from https://hub.github.com/ !' |
133 | 139 | ) |
134 | 140 |
|
| 141 | +def ensure_gothub_is_installed(): |
| 142 | + '''Check whether gothub (to draft github releases) is installed. |
| 143 | + If not, throw an error with an installation note. |
| 144 | + ''' |
| 145 | + try: |
| 146 | + assert subprocess.call(["gothub", "--version"]) == 0 |
| 147 | + except (OSError, AssertionError): |
| 148 | + raise OSError( |
| 149 | + 'The gothub command-line tool is needed for ' |
| 150 | + 'drafting releases on github. ' |
| 151 | + 'Please install gothub from ' |
| 152 | + 'https://github.com/itchio/gothub !' |
| 153 | + ) |
| 154 | + |
| 155 | +def ensure_gitpulls_is_installed(): |
| 156 | + '''Check whether the gem git-pulls (to get github pull requests) is |
| 157 | + installed. |
| 158 | + If not, throw an error with an installation note. |
| 159 | + ''' |
| 160 | + try: |
| 161 | + assert subprocess.call(["git", "pulls"]) == 0 |
| 162 | + except (OSError, AssertionError): |
| 163 | + raise OSError( |
| 164 | + 'The gothub command-line tool is needed for ' |
| 165 | + 'checking the pull request text from the release. ' |
| 166 | + 'Please install git-pulls ' |
| 167 | + 'from https://github.com/schacon/git-pulls !' |
| 168 | + ) |
| 169 | + |
| 170 | +def check_release_tools(): |
| 171 | + '''Return whether git-pulls and gothub are installed (needed for |
| 172 | + drafting the github release from CLI). If not, ask whether user |
| 173 | + wants to continue and draft the release manually (if this is not |
| 174 | + the case, raise exception!). |
| 175 | + ''' |
| 176 | + try: |
| 177 | + ensure_gitpulls_is_installed() |
| 178 | + ensure_gothub_is_installed() |
| 179 | + return True |
| 180 | + except OSError: |
| 181 | + answer = '' |
| 182 | + accept = ['y', 'yes', 'n', 'no'] |
| 183 | + while answer not in accept: |
| 184 | + answer = input( |
| 185 | + '!!! You do not have all required tools installed to ' |
| 186 | + 'automatically draft a release. Do you want to continue ' |
| 187 | + 'and manually draft the release on github afterwards?\n' |
| 188 | + '[y/N] ').lower() |
| 189 | + if not answer: |
| 190 | + answer = 'n' |
| 191 | + if answer == 'n' or answer == 'no': |
| 192 | + raise |
| 193 | + else: |
| 194 | + return False |
| 195 | + |
135 | 196 | def current_branch(): |
136 | 197 | '''Return current git branch name.''' |
137 | 198 | # get the current branch name, strip trailing whitespaces using rstrip() |
@@ -172,14 +233,42 @@ def git_status(): |
172 | 233 | ) |
173 | 234 | print (output) |
174 | 235 |
|
| 236 | +def get_pullrequest_message(release_version): |
| 237 | + '''Fetch message from open pull request corresponding to this |
| 238 | + release_version. |
| 239 | + ''' |
| 240 | + fetched = subprocess.check_output(['git', 'pulls', 'update']) |
| 241 | + pr_number = None |
| 242 | + for line in fetched.split('\n'): |
| 243 | + if "PyCOMPLETE:release/v{}".format(release_version) in line: |
| 244 | + pr_number = line.split()[0] |
| 245 | + break |
| 246 | + if pr_number is None: |
| 247 | + raise EnvironmentError( |
| 248 | + 'Could not find open pull request for this release version. ' |
| 249 | + 'Did you properly initiate the release process ' |
| 250 | + '(step 1 in ./release.py --help)?') |
| 251 | + text = subprocess.check_output(['git', 'pulls', 'show', pr_number]) |
| 252 | + output = [] |
| 253 | + for line in text.split('\n')[5:]: |
| 254 | + if line != '------------': |
| 255 | + output.append(line) |
| 256 | + else: |
| 257 | + break |
| 258 | + output[0] = output[0][11:] # remove "Title : " |
| 259 | + return '\n'.join(output) |
| 260 | + |
175 | 261 |
|
176 | 262 | # DEFINE TWO STEPS FOR RELEASE PROCESS: |
177 | 263 |
|
178 | 264 | def init_release(part): |
179 | 265 | '''Initialise release process.''' |
180 | | - if not current_branch() == 'develop': |
| 266 | + if not current_branch() == 'develop' and not ( |
| 267 | + current_branch()[:7] == 'hotfix/' and |
| 268 | + part == 'patch'): |
181 | 269 | raise EnvironmentError( |
182 | | - 'Releases can only be initiated from the develop branch!') |
| 270 | + 'Releases can only be initiated from the develop branch! ' |
| 271 | + '(Releasing a patch is allowed from a hotfix/ branch as well.)') |
183 | 272 | if is_worktree_dirty(): |
184 | 273 | git_status() |
185 | 274 | raise EnvironmentError('Release process can only be initiated on ' |
@@ -213,6 +302,11 @@ def finalise_release(): |
213 | 302 | raise EnvironmentError('The PyHEADTAIL tests fail. Please fix ' |
214 | 303 | 'the tests first!') |
215 | 304 | print ('*** The PyHEADTAIL tests have successfully terminated.') |
| 305 | + |
| 306 | + # all tools installed to automatically draft release? |
| 307 | + draft_release = check_release_tools() |
| 308 | + |
| 309 | + # bump version file |
216 | 310 | new_version = establish_new_version(version_location) |
217 | 311 |
|
218 | 312 | # make sure to push any possible release branch commits |
@@ -240,12 +334,23 @@ def finalise_release(): |
240 | 334 | assert subprocess.call(["git", "merge", "master"]) == 0 |
241 | 335 | assert subprocess.call(["git", "push", "origin", "develop"]) == 0 |
242 | 336 |
|
243 | | - # TO DO: publish github release (with text from pull request open in editor) |
244 | | - |
245 | 337 | # delete release branch |
246 | 338 | assert subprocess.call(["git", "branch", "-d", rbranch]) == 0 |
247 | 339 | assert subprocess.call(["git", "push", "origin", ":" + rbranch]) == 0 |
248 | 340 |
|
| 341 | + # publish github release (with message from pull request) |
| 342 | + if draft_release: |
| 343 | + message = get_pullrequest_message(new_version) |
| 344 | + assert subprocess.call( |
| 345 | + ['gothub', 'release', '-u', github_user, '-r', github_repo, |
| 346 | + '-t', 'v' + new_version, |
| 347 | + '-n', '"PyHEADTAIL v{}"'.format(new_version), |
| 348 | + '-d', '"{}"'.format(message), |
| 349 | + '-c', 'master']) |
| 350 | + else: |
| 351 | + print ('*** Remember to manually draft this release from the ' |
| 352 | + 'github website.') |
| 353 | + |
249 | 354 | # ALGORITHM FOR RELEASE PROCESS: |
250 | 355 | if __name__ == '__main__': |
251 | 356 | print ('*** Current working directory:\n' + os.getcwd() + '\n') |
|
0 commit comments