13
13
14
14
"""
15
15
16
- # Value of the mode flag for a v1 release
17
- V1_MODE = 'v1-release'
18
-
19
- # Value of the mode flag for a v2 release
20
- V2_MODE = 'v2-release'
21
-
22
- SOURCE_BRANCH_FOR_MODE = { V1_MODE : 'releases/v2' , V2_MODE : 'main' }
23
- TARGET_BRANCH_FOR_MODE = { V1_MODE : 'releases/v1' , V2_MODE : 'releases/v2' }
16
+ SOURCE_BRANCH = 'main'
17
+ TARGET_BRANCH = 'releases/v2'
24
18
25
19
# Name of the remote
26
20
ORIGIN = 'origin'
@@ -32,39 +26,37 @@ def run_git(*args, allow_non_zero_exit_code=False):
32
26
cmd = ['git' , * args ]
33
27
p = subprocess .run (cmd , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
34
28
if not allow_non_zero_exit_code and p .returncode != 0 :
35
- raise Exception ('Call to ' + ' ' .join (cmd ) + ' exited with code ' + str ( p .returncode ) + ' stderr:' + p .stderr .decode (' ascii' ) )
29
+ raise Exception (f 'Call to { " " .join (cmd )} exited with code { p .returncode } stderr: { p .stderr .decode (" ascii" ) } .' )
36
30
return p .stdout .decode ('ascii' )
37
31
38
32
# Returns true if the given branch exists on the origin remote
39
33
def branch_exists_on_remote (branch_name ):
40
34
return run_git ('ls-remote' , '--heads' , ORIGIN , branch_name ).strip () != ''
41
35
42
36
# Opens a PR from the given branch to the target branch
43
- def open_pr (
44
- repo , all_commits , source_branch_short_sha , new_branch_name , source_branch , target_branch ,
45
- conductor , is_v2_release , labels , conflicted_files ):
37
+ def open_pr (repo , all_commits , source_branch_short_sha , new_branch_name , conductor ):
46
38
# Sort the commits into the pull requests that introduced them,
47
39
# and any commits that don't have a pull request
48
40
pull_requests = []
49
41
commits_without_pull_requests = []
50
42
for commit in all_commits :
51
- pr = get_pr_for_commit (repo , commit )
43
+ pr = get_pr_for_commit (commit )
52
44
53
45
if pr is None :
54
46
commits_without_pull_requests .append (commit )
55
47
elif not any (p for p in pull_requests if p .number == pr .number ):
56
48
pull_requests .append (pr )
57
49
58
- print ('Found ' + str ( len (pull_requests )) + ' pull requests' )
59
- print ('Found ' + str ( len (commits_without_pull_requests )) + ' commits not in a pull request' )
50
+ print (f 'Found { len (pull_requests )} pull requests. ' )
51
+ print (f 'Found { len (commits_without_pull_requests )} commits not in a pull request. ' )
60
52
61
53
# Sort PRs and commits by age
62
54
pull_requests = sorted (pull_requests , key = lambda pr : pr .number )
63
55
commits_without_pull_requests = sorted (commits_without_pull_requests , key = lambda c : c .commit .author .date )
64
56
65
57
# Start constructing the body text
66
58
body = []
67
- body .append ('Merging ' + source_branch_short_sha + ' into ' + target_branch )
59
+ body .append (f 'Merging { source_branch_short_sha } into { TARGET_BRANCH } .' )
68
60
69
61
body .append ('' )
70
62
body .append (f'Conductor for this PR is @{ conductor } .' )
@@ -87,50 +79,33 @@ def open_pr(
87
79
88
80
body .append ('' )
89
81
body .append ('Please do the following:' )
90
- if len (conflicted_files ) > 0 :
91
- body .append (' - [ ] Ensure `package.json` file contains the correct version.' )
92
- body .append (' - [ ] Add commits to this branch to resolve the merge conflicts ' +
93
- 'in the following files:' )
94
- body .extend ([f' - [ ] `{ file } `' for file in conflicted_files ])
95
- body .append (' - [ ] Ensure another maintainer has reviewed the additional commits you added to this ' +
96
- 'branch to resolve the merge conflicts.' )
97
82
body .append (' - [ ] Ensure the CHANGELOG displays the correct version and date.' )
98
83
body .append (' - [ ] Ensure the CHANGELOG includes all relevant, user-facing changes since the last release.' )
99
- body .append (' - [ ] Check that there are not any unexpected commits being merged into the ' + target_branch + ' branch.' )
84
+ body .append (f ' - [ ] Check that there are not any unexpected commits being merged into the { TARGET_BRANCH } branch.' )
100
85
body .append (' - [ ] Ensure the docs team is aware of any documentation changes that need to be released.' )
101
-
102
- if not is_v2_release :
103
- body .append (' - [ ] Remove and re-add the "Update dependencies" label to the PR to trigger just this workflow.' )
104
- body .append (' - [ ] Wait for the "Update dependencies" workflow to push a commit updating the dependencies.' )
105
- body .append (' - [ ] Mark the PR as ready for review to trigger the full set of PR checks.' )
106
-
107
86
body .append (' - [ ] Approve and merge this PR. Make sure `Create a merge commit` is selected rather than `Squash and merge` or `Rebase and merge`.' )
87
+ body .append (' - [ ] Merge the mergeback PR that will automatically be created once this PR is merged.' )
108
88
109
- if is_v2_release :
110
- body .append (' - [ ] Merge the mergeback PR that will automatically be created once this PR is merged.' )
111
- body .append (' - [ ] Merge the v1 release PR that will automatically be created once this PR is merged.' )
112
-
113
- title = 'Merge ' + source_branch + ' into ' + target_branch
89
+ title = f'Merge { SOURCE_BRANCH } into { TARGET_BRANCH } '
114
90
115
91
# Create the pull request
116
92
# PR checks won't be triggered on PRs created by Actions. Therefore mark the PR as draft so that
117
93
# a maintainer can take the PR out of draft, thereby triggering the PR checks.
118
- pr = repo .create_pull (title = title , body = '\n ' .join (body ), head = new_branch_name , base = target_branch , draft = True )
119
- pr .add_to_labels (* labels )
120
- print ('Created PR #' + str (pr .number ))
94
+ pr = repo .create_pull (title = title , body = '\n ' .join (body ), head = new_branch_name , base = TARGET_BRANCH , draft = True )
95
+ print (f'Created PR #{ pr .number } ' )
121
96
122
97
# Assign the conductor
123
98
pr .add_to_assignees (conductor )
124
- print ('Assigned PR to ' + conductor )
99
+ print (f 'Assigned PR to { conductor } ' )
125
100
126
101
# Gets a list of the SHAs of all commits that have happened on the source branch
127
102
# since the last release to the target branch.
128
103
# This will not include any commits that exist on the target branch
129
104
# that aren't on the source branch.
130
- def get_commit_difference (repo , source_branch , target_branch ):
105
+ def get_commit_difference (repo ):
131
106
# Passing split nothing means that the empty string splits to nothing: compare `''.split() == []`
132
107
# to `''.split('\n') == ['']`.
133
- commits = run_git ('log' , '--pretty=format:%H' , ORIGIN + '/' + target_branch + '..' + ORIGIN + '/' + source_branch ).strip ().split ()
108
+ commits = run_git ('log' , '--pretty=format:%H' , f' { ORIGIN } / { TARGET_BRANCH } .. { ORIGIN } / { SOURCE_BRANCH } ' ).strip ().split ()
134
109
135
110
# Convert to full-fledged commit objects
136
111
commits = [repo .get_commit (c ) for c in commits ]
@@ -146,13 +121,13 @@ def is_pr_merge_commit(commit):
146
121
def get_truncated_commit_message (commit ):
147
122
message = commit .commit .message .split ('\n ' )[0 ]
148
123
if len (message ) > 60 :
149
- return message [:57 ] + ' ...'
124
+ return f' { message [:57 ]} ...'
150
125
else :
151
126
return message
152
127
153
128
# Converts a commit into the PR that introduced it to the source branch.
154
129
# Returns the PR object, or None if no PR could be found.
155
- def get_pr_for_commit (repo , commit ):
130
+ def get_pr_for_commit (commit ):
156
131
prs = commit .get_pulls ()
157
132
158
133
if prs .totalCount > 0 :
@@ -186,7 +161,7 @@ def update_changelog(version):
186
161
else :
187
162
content = EMPTY_CHANGELOG
188
163
189
- newContent = content .replace ('[UNRELEASED]' , version + ' - ' + get_today_string (), 1 )
164
+ newContent = content .replace ('[UNRELEASED]' , f'$ { version } - { get_today_string ()} ' , 1 )
190
165
191
166
with open ('CHANGELOG.md' , 'w' ) as f :
192
167
f .write (newContent )
@@ -207,16 +182,6 @@ def main():
207
182
required = True ,
208
183
help = 'The nwo of the repository, for example github/codeql-action.'
209
184
)
210
- parser .add_argument (
211
- '--mode' ,
212
- type = str ,
213
- required = True ,
214
- choices = [V2_MODE , V1_MODE ],
215
- help = f"Which release to perform. '{ V2_MODE } ' uses { SOURCE_BRANCH_FOR_MODE [V2_MODE ]} as the source " +
216
- f"branch and { TARGET_BRANCH_FOR_MODE [V2_MODE ]} as the target branch. " +
217
- f"'{ V1_MODE } ' uses { SOURCE_BRANCH_FOR_MODE [V1_MODE ]} as the source branch and " +
218
- f"{ TARGET_BRANCH_FOR_MODE [V1_MODE ]} as the target branch."
219
- )
220
185
parser .add_argument (
221
186
'--conductor' ,
222
187
type = str ,
@@ -226,110 +191,46 @@ def main():
226
191
227
192
args = parser .parse_args ()
228
193
229
- source_branch = SOURCE_BRANCH_FOR_MODE [args .mode ]
230
- target_branch = TARGET_BRANCH_FOR_MODE [args .mode ]
231
-
232
194
repo = Github (args .github_token ).get_repo (args .repository_nwo )
233
195
version = get_current_version ()
234
196
235
- if args .mode == V1_MODE :
236
- # Change the version number to a v1 equivalent
237
- version = get_current_version ()
238
- version = f'1{ version [1 :]} '
239
-
240
197
# Print what we intend to go
241
- print ('Considering difference between ' + source_branch + ' and ' + target_branch )
242
- source_branch_short_sha = run_git ('rev-parse' , '--short' , ORIGIN + '/' + source_branch ).strip ()
243
- print ('Current head of ' + source_branch + ' is ' + source_branch_short_sha )
198
+ print (f 'Considering difference between { SOURCE_BRANCH } and { TARGET_BRANCH } ...' )
199
+ source_branch_short_sha = run_git ('rev-parse' , '--short' , f' { ORIGIN } / { SOURCE_BRANCH } ' ).strip ()
200
+ print (f 'Current head of { SOURCE_BRANCH } is { source_branch_short_sha } .' )
244
201
245
202
# See if there are any commits to merge in
246
- commits = get_commit_difference (repo = repo , source_branch = source_branch , target_branch = target_branch )
203
+ commits = get_commit_difference (repo = repo )
247
204
if len (commits ) == 0 :
248
- print ('No commits to merge from ' + source_branch + ' to ' + target_branch )
205
+ print (f 'No commits to merge from { SOURCE_BRANCH } to { TARGET_BRANCH } .' )
249
206
return
250
207
251
208
# The branch name is based off of the name of branch being merged into
252
209
# and the SHA of the branch being merged from. Thus if the branch already
253
210
# exists we can assume we don't need to recreate it.
254
- new_branch_name = 'update-v' + version + '-' + source_branch_short_sha
255
- print ('Branch name is ' + new_branch_name )
211
+ new_branch_name = f 'update-v{ version } - { source_branch_short_sha } '
212
+ print (f 'Branch name is { new_branch_name } .' )
256
213
257
214
# Check if the branch already exists. If so we can abort as this script
258
215
# has already run on this combination of branches.
259
216
if branch_exists_on_remote (new_branch_name ):
260
- print ('Branch ' + new_branch_name + ' already exists. Nothing to do.' )
217
+ print (f 'Branch { new_branch_name } already exists. Nothing to do.' )
261
218
return
262
219
263
220
# Create the new branch and push it to the remote
264
- print ('Creating branch ' + new_branch_name )
265
-
266
- # The process of creating the v1 release can run into merge conflicts. We commit the unresolved
267
- # conflicts so a maintainer can easily resolve them (vs erroring and requiring maintainers to
268
- # reconstruct the release manually)
269
- conflicted_files = []
270
-
271
- if args .mode == V1_MODE :
272
- # If we're performing a backport, start from the target branch
273
- print (f'Creating { new_branch_name } from the { ORIGIN } /{ target_branch } branch' )
274
- run_git ('checkout' , '-b' , new_branch_name , f'{ ORIGIN } /{ target_branch } ' )
275
-
276
- # Revert the commit that we made as part of the last release that updated the version number and
277
- # changelog to refer to 1.x.x variants. This avoids merge conflicts in the changelog and
278
- # package.json files when we merge in the v2 branch.
279
- # This commit will not exist the first time we release the v1 branch from the v2 branch, so we
280
- # use `git log --grep` to conditionally revert the commit.
281
- print ('Reverting the 1.x.x version number and changelog updates from the last release to avoid conflicts' )
282
- v1_update_commits = run_git ('log' , '--grep' , '^Update version and changelog for v' , '--format=%H' ).split ()
283
-
284
- if len (v1_update_commits ) > 0 :
285
- print (f' Reverting { v1_update_commits [0 ]} ' )
286
- # Only revert the newest commit as older ones will already have been reverted in previous
287
- # releases.
288
- run_git ('revert' , v1_update_commits [0 ], '--no-edit' )
289
-
290
- # Also revert the "Update checked-in dependencies" commit created by Actions.
291
- update_dependencies_commit = run_git ('log' , '--grep' , '^Update checked-in dependencies' , '--format=%H' ).split ()[0 ]
292
- print (f' Reverting { update_dependencies_commit } ' )
293
- run_git ('revert' , update_dependencies_commit , '--no-edit' )
294
-
295
- else :
296
- print (' Nothing to revert.' )
297
-
298
- print (f'Merging { ORIGIN } /{ source_branch } into the release prep branch' )
299
- # Commit any conflicts (see the comment for `conflicted_files`)
300
- run_git ('merge' , f'{ ORIGIN } /{ source_branch } ' , allow_non_zero_exit_code = True )
301
- conflicted_files = run_git ('diff' , '--name-only' , '--diff-filter' , 'U' ).splitlines ()
302
- if len (conflicted_files ) > 0 :
303
- run_git ('add' , '.' )
304
- run_git ('commit' , '--no-edit' )
305
-
306
- # Migrate the package version number from a v2 version number to a v1 version number
307
- print (f'Setting version number to { version } ' )
308
- subprocess .check_output (['npm' , 'version' , version , '--no-git-tag-version' ])
309
- run_git ('add' , 'package.json' , 'package-lock.json' )
310
-
311
- # Migrate the changelog notes from v2 version numbers to v1 version numbers
312
- print ('Migrating changelog notes from v2 to v1' )
313
- subprocess .check_output (['sed' , '-i' , 's/^## 2\./## 1./g' , 'CHANGELOG.md' ])
314
-
315
- # Remove changelog notes from v2 that don't apply to v1
316
- subprocess .check_output (['sed' , '-i' , '/^- \[v2+ only\]/d' , 'CHANGELOG.md' ])
317
-
318
- # Amend the commit generated by `npm version` to update the CHANGELOG
319
- run_git ('add' , 'CHANGELOG.md' )
320
- run_git ('commit' , '-m' , f'Update version and changelog for v{ version } ' )
321
- else :
322
- # If we're performing a standard release, there won't be any new commits on the target branch,
323
- # as these will have already been merged back into the source branch. Therefore we can just
324
- # start from the source branch.
325
- run_git ('checkout' , '-b' , new_branch_name , f'{ ORIGIN } /{ source_branch } ' )
221
+ print (f'Creating branch { new_branch_name } .' )
222
+
223
+ # If we're performing a standard release, there won't be any new commits on the target branch,
224
+ # as these will have already been merged back into the source branch. Therefore we can just
225
+ # start from the source branch.
226
+ run_git ('checkout' , '-b' , new_branch_name , f'{ ORIGIN } /{ SOURCE_BRANCH } ' )
326
227
327
- print ('Updating changelog' )
328
- update_changelog (version )
228
+ print ('Updating changelog' )
229
+ update_changelog (version )
329
230
330
- # Create a commit that updates the CHANGELOG
331
- run_git ('add' , 'CHANGELOG.md' )
332
- run_git ('commit' , '-m' , f'Update changelog for v{ version } ' )
231
+ # Create a commit that updates the CHANGELOG
232
+ run_git ('add' , 'CHANGELOG.md' )
233
+ run_git ('commit' , '-m' , f'Update changelog for v{ version } ' )
333
234
334
235
run_git ('push' , ORIGIN , new_branch_name )
335
236
@@ -339,12 +240,7 @@ def main():
339
240
commits ,
340
241
source_branch_short_sha ,
341
242
new_branch_name ,
342
- source_branch = source_branch ,
343
- target_branch = target_branch ,
344
243
conductor = args .conductor ,
345
- is_v2_release = args .mode == V2_MODE ,
346
- labels = ['Update dependencies' ] if args .mode == V1_MODE else [],
347
- conflicted_files = conflicted_files
348
244
)
349
245
350
246
if __name__ == '__main__' :
0 commit comments