1313
1414"""
1515
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'
2418
2519# Name of the remote
2620ORIGIN = 'origin'
@@ -32,39 +26,37 @@ def run_git(*args, allow_non_zero_exit_code=False):
3226 cmd = ['git' , * args ]
3327 p = subprocess .run (cmd , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
3428 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" ) } .' )
3630 return p .stdout .decode ('ascii' )
3731
3832# Returns true if the given branch exists on the origin remote
3933def branch_exists_on_remote (branch_name ):
4034 return run_git ('ls-remote' , '--heads' , ORIGIN , branch_name ).strip () != ''
4135
4236# 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 ):
4638 # Sort the commits into the pull requests that introduced them,
4739 # and any commits that don't have a pull request
4840 pull_requests = []
4941 commits_without_pull_requests = []
5042 for commit in all_commits :
51- pr = get_pr_for_commit (repo , commit )
43+ pr = get_pr_for_commit (commit )
5244
5345 if pr is None :
5446 commits_without_pull_requests .append (commit )
5547 elif not any (p for p in pull_requests if p .number == pr .number ):
5648 pull_requests .append (pr )
5749
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. ' )
6052
6153 # Sort PRs and commits by age
6254 pull_requests = sorted (pull_requests , key = lambda pr : pr .number )
6355 commits_without_pull_requests = sorted (commits_without_pull_requests , key = lambda c : c .commit .author .date )
6456
6557 # Start constructing the body text
6658 body = []
67- body .append ('Merging ' + source_branch_short_sha + ' into ' + target_branch )
59+ body .append (f 'Merging { source_branch_short_sha } into { TARGET_BRANCH } .' )
6860
6961 body .append ('' )
7062 body .append (f'Conductor for this PR is @{ conductor } .' )
@@ -87,50 +79,33 @@ def open_pr(
8779
8880 body .append ('' )
8981 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.' )
9782 body .append (' - [ ] Ensure the CHANGELOG displays the correct version and date.' )
9883 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.' )
10085 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-
10786 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.' )
10888
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 } '
11490
11591 # Create the pull request
11692 # PR checks won't be triggered on PRs created by Actions. Therefore mark the PR as draft so that
11793 # 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 } ' )
12196
12297 # Assign the conductor
12398 pr .add_to_assignees (conductor )
124- print ('Assigned PR to ' + conductor )
99+ print (f 'Assigned PR to { conductor } ' )
125100
126101# Gets a list of the SHAs of all commits that have happened on the source branch
127102# since the last release to the target branch.
128103# This will not include any commits that exist on the target branch
129104# that aren't on the source branch.
130- def get_commit_difference (repo , source_branch , target_branch ):
105+ def get_commit_difference (repo ):
131106 # Passing split nothing means that the empty string splits to nothing: compare `''.split() == []`
132107 # 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 ()
134109
135110 # Convert to full-fledged commit objects
136111 commits = [repo .get_commit (c ) for c in commits ]
@@ -146,13 +121,13 @@ def is_pr_merge_commit(commit):
146121def get_truncated_commit_message (commit ):
147122 message = commit .commit .message .split ('\n ' )[0 ]
148123 if len (message ) > 60 :
149- return message [:57 ] + ' ...'
124+ return f' { message [:57 ]} ...'
150125 else :
151126 return message
152127
153128# Converts a commit into the PR that introduced it to the source branch.
154129# 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 ):
156131 prs = commit .get_pulls ()
157132
158133 if prs .totalCount > 0 :
@@ -186,7 +161,7 @@ def update_changelog(version):
186161 else :
187162 content = EMPTY_CHANGELOG
188163
189- newContent = content .replace ('[UNRELEASED]' , version + ' - ' + get_today_string (), 1 )
164+ newContent = content .replace ('[UNRELEASED]' , f' { version } - { get_today_string ()} ' , 1 )
190165
191166 with open ('CHANGELOG.md' , 'w' ) as f :
192167 f .write (newContent )
@@ -207,16 +182,6 @@ def main():
207182 required = True ,
208183 help = 'The nwo of the repository, for example github/codeql-action.'
209184 )
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- )
220185 parser .add_argument (
221186 '--conductor' ,
222187 type = str ,
@@ -226,110 +191,46 @@ def main():
226191
227192 args = parser .parse_args ()
228193
229- source_branch = SOURCE_BRANCH_FOR_MODE [args .mode ]
230- target_branch = TARGET_BRANCH_FOR_MODE [args .mode ]
231-
232194 repo = Github (args .github_token ).get_repo (args .repository_nwo )
233195 version = get_current_version ()
234196
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-
240197 # 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 } .' )
244201
245202 # 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 )
247204 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 } .' )
249206 return
250207
251208 # The branch name is based off of the name of branch being merged into
252209 # and the SHA of the branch being merged from. Thus if the branch already
253210 # 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 } .' )
256213
257214 # Check if the branch already exists. If so we can abort as this script
258215 # has already run on this combination of branches.
259216 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.' )
261218 return
262219
263220 # 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 } ' )
326227
327- print ('Updating changelog' )
328- update_changelog (version )
228+ print ('Updating changelog' )
229+ update_changelog (version )
329230
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 } ' )
333234
334235 run_git ('push' , ORIGIN , new_branch_name )
335236
@@ -339,12 +240,7 @@ def main():
339240 commits ,
340241 source_branch_short_sha ,
341242 new_branch_name ,
342- source_branch = source_branch ,
343- target_branch = target_branch ,
344243 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
348244 )
349245
350246if __name__ == '__main__' :
0 commit comments