Skip to content

Commit 4d6e9c0

Browse files
authored
Merge pull request #995 from github/henrymercer/update-release-process
Update release process to maintain both v2 and v1 releases
2 parents 6d1f0a0 + 839aa81 commit 4d6e9c0

File tree

5 files changed

+203
-77
lines changed

5 files changed

+203
-77
lines changed

.github/update-release-branch.py

Lines changed: 144 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1+
import argparse
12
import datetime
23
from github import Github
3-
import random
4-
import requests
5-
import subprocess
6-
import sys
74
import json
8-
import datetime
95
import os
6+
import subprocess
107

118
EMPTY_CHANGELOG = """# CodeQL Action and CodeQL Runner Changelog
129
@@ -16,12 +13,12 @@
1613
1714
"""
1815

19-
# The branch being merged from.
20-
# This is the one that contains day-to-day development work.
21-
MAIN_BRANCH = 'main'
22-
# The branch being merged into.
23-
# This is the release branch that users reference.
24-
LATEST_RELEASE_BRANCH = 'v1'
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+
2522
# Name of the remote
2623
ORIGIN = 'origin'
2724

@@ -38,8 +35,8 @@ def run_git(*args):
3835
def branch_exists_on_remote(branch_name):
3936
return run_git('ls-remote', '--heads', ORIGIN, branch_name).strip() != ''
4037

41-
# Opens a PR from the given branch to the release branch
42-
def open_pr(repo, all_commits, short_main_sha, branch_name):
38+
# Opens a PR from the given branch to the target branch
39+
def open_pr(repo, all_commits, source_branch_short_sha, new_branch_name, source_branch, target_branch, conductor, is_v2_release, labels):
4340
# Sort the commits into the pull requests that introduced them,
4441
# and any commits that don't have a pull request
4542
pull_requests = []
@@ -61,9 +58,8 @@ def open_pr(repo, all_commits, short_main_sha, branch_name):
6158

6259
# Start constructing the body text
6360
body = []
64-
body.append('Merging ' + short_main_sha + ' into ' + LATEST_RELEASE_BRANCH)
61+
body.append('Merging ' + source_branch_short_sha + ' into ' + target_branch)
6562

66-
conductor = get_conductor(repo, pull_requests, commits_without_pull_requests)
6763
body.append('')
6864
body.append('Conductor for this PR is @' + conductor)
6965

@@ -80,43 +76,40 @@ def open_pr(repo, all_commits, short_main_sha, branch_name):
8076
body.append('')
8177
body.append('Contains the following commits not from a pull request:')
8278
for commit in commits_without_pull_requests:
83-
body.append('- ' + commit.sha + ' - ' + get_truncated_commit_message(commit) + ' (@' + commit.author.login + ')')
79+
author_description = ' (@' + commit.author.login + ')' if commit.author is not None else ''
80+
body.append('- ' + commit.sha + ' - ' + get_truncated_commit_message(commit) + author_description)
8481

8582
body.append('')
8683
body.append('Please review the following:')
8784
body.append(' - [ ] The CHANGELOG displays the correct version and date.')
8885
body.append(' - [ ] The CHANGELOG includes all relevant, user-facing changes since the last release.')
89-
body.append(' - [ ] There are no unexpected commits being merged into the ' + LATEST_RELEASE_BRANCH + ' branch.')
86+
body.append(' - [ ] There are no unexpected commits being merged into the ' + target_branch + ' branch.')
9087
body.append(' - [ ] The docs team is aware of any documentation changes that need to be released.')
91-
body.append(' - [ ] The mergeback PR is merged back into ' + MAIN_BRANCH + ' after this PR is merged.')
88+
if is_v2_release:
89+
body.append(' - [ ] The mergeback PR is merged back into ' + source_branch + ' after this PR is merged.')
90+
body.append(' - [ ] The v1 release PR is merged after this PR is merged.')
9291

93-
title = 'Merge ' + MAIN_BRANCH + ' into ' + LATEST_RELEASE_BRANCH
92+
title = 'Merge ' + source_branch + ' into ' + target_branch
9493

9594
# Create the pull request
9695
# PR checks won't be triggered on PRs created by Actions. Therefore mark the PR as draft so that
9796
# a maintainer can take the PR out of draft, thereby triggering the PR checks.
98-
pr = repo.create_pull(title=title, body='\n'.join(body), head=branch_name, base=LATEST_RELEASE_BRANCH, draft=True)
97+
pr = repo.create_pull(title=title, body='\n'.join(body), head=new_branch_name, base=target_branch, draft=True)
98+
pr.add_to_labels(*labels)
9999
print('Created PR #' + str(pr.number))
100100

101101
# Assign the conductor
102102
pr.add_to_assignees(conductor)
103103
print('Assigned PR to ' + conductor)
104104

105-
# Gets the person who should be in charge of the mergeback PR
106-
def get_conductor(repo, pull_requests, other_commits):
107-
# If there are any PRs then use whoever merged the last one
108-
if len(pull_requests) > 0:
109-
return get_merger_of_pr(repo, pull_requests[-1])
110-
111-
# Otherwise take the author of the latest commit
112-
return other_commits[-1].author.login
113-
114-
# Gets a list of the SHAs of all commits that have happened on main
115-
# since the release branched off.
116-
# This will not include any commits that exist on the release branch
117-
# that aren't on main.
118-
def get_commit_difference(repo):
119-
commits = run_git('log', '--pretty=format:%H', ORIGIN + '/' + LATEST_RELEASE_BRANCH + '..' + ORIGIN + '/' + MAIN_BRANCH).strip().split('\n')
105+
# Gets a list of the SHAs of all commits that have happened on the source branch
106+
# since the last release to the target branch.
107+
# This will not include any commits that exist on the target branch
108+
# that aren't on the source branch.
109+
def get_commit_difference(repo, source_branch, target_branch):
110+
# Passing split nothing means that the empty string splits to nothing: compare `''.split() == []`
111+
# to `''.split('\n') == ['']`.
112+
commits = run_git('log', '--pretty=format:%H', ORIGIN + '/' + target_branch + '..' + ORIGIN + '/' + source_branch).strip().split()
120113

121114
# Convert to full-fledged commit objects
122115
commits = [repo.get_commit(c) for c in commits]
@@ -136,7 +129,7 @@ def get_truncated_commit_message(commit):
136129
else:
137130
return message
138131

139-
# Converts a commit into the PR that introduced it to the main branch.
132+
# Converts a commit into the PR that introduced it to the source branch.
140133
# Returns the PR object, or None if no PR could be found.
141134
def get_pr_for_commit(repo, commit):
142135
prs = commit.get_pulls()
@@ -179,29 +172,69 @@ def update_changelog(version):
179172

180173

181174
def main():
182-
if len(sys.argv) != 3:
183-
raise Exception('Usage: update-release.branch.py <github token> <repository nwo>')
184-
github_token = sys.argv[1]
185-
repository_nwo = sys.argv[2]
175+
parser = argparse.ArgumentParser('update-release-branch.py')
176+
177+
parser.add_argument(
178+
'--github-token',
179+
type=str,
180+
required=True,
181+
help='GitHub token, typically from GitHub Actions.'
182+
)
183+
parser.add_argument(
184+
'--repository-nwo',
185+
type=str,
186+
required=True,
187+
help='The nwo of the repository, for example github/codeql-action.'
188+
)
189+
parser.add_argument(
190+
'--mode',
191+
type=str,
192+
required=True,
193+
choices=[V2_MODE, V1_MODE],
194+
help=f"Which release to perform. '{V2_MODE}' uses main as the source branch and v2 as the target branch. " +
195+
f"'{V1_MODE}' uses v2 as the source branch and v1 as the target branch."
196+
)
197+
parser.add_argument(
198+
'--conductor',
199+
type=str,
200+
required=True,
201+
help='The GitHub handle of the person who is conducting the release process.'
202+
)
203+
204+
args = parser.parse_args()
205+
206+
if args.mode == V2_MODE:
207+
source_branch = 'main'
208+
target_branch = 'v2'
209+
elif args.mode == V1_MODE:
210+
source_branch = 'v2'
211+
target_branch = 'v1'
212+
else:
213+
raise ValueError(f"Unexpected value for release mode: '{args.mode}'")
186214

187-
repo = Github(github_token).get_repo(repository_nwo)
215+
repo = Github(args.github_token).get_repo(args.repository_nwo)
188216
version = get_current_version()
189217

218+
if args.mode == V1_MODE:
219+
# Change the version number to a v1 equivalent
220+
version = get_current_version()
221+
version = f'1{version[1:]}'
222+
190223
# Print what we intend to go
191-
print('Considering difference between ' + MAIN_BRANCH + ' and ' + LATEST_RELEASE_BRANCH)
192-
short_main_sha = run_git('rev-parse', '--short', ORIGIN + '/' + MAIN_BRANCH).strip()
193-
print('Current head of ' + MAIN_BRANCH + ' is ' + short_main_sha)
224+
print('Considering difference between ' + source_branch + ' and ' + target_branch)
225+
source_branch_short_sha = run_git('rev-parse', '--short', ORIGIN + '/' + source_branch).strip()
226+
print('Current head of ' + source_branch + ' is ' + source_branch_short_sha)
194227

195228
# See if there are any commits to merge in
196-
commits = get_commit_difference(repo)
229+
commits = get_commit_difference(repo=repo, source_branch=source_branch, target_branch=target_branch)
197230
if len(commits) == 0:
198-
print('No commits to merge from ' + MAIN_BRANCH + ' to ' + LATEST_RELEASE_BRANCH)
231+
print('No commits to merge from ' + source_branch + ' to ' + target_branch)
199232
return
200233

201234
# The branch name is based off of the name of branch being merged into
202235
# and the SHA of the branch being merged from. Thus if the branch already
203236
# exists we can assume we don't need to recreate it.
204-
new_branch_name = 'update-v' + version + '-' + short_main_sha
237+
new_branch_name = 'update-v' + version + '-' + source_branch_short_sha
205238
print('Branch name is ' + new_branch_name)
206239

207240
# Check if the branch already exists. If so we can abort as this script
@@ -212,19 +245,76 @@ def main():
212245

213246
# Create the new branch and push it to the remote
214247
print('Creating branch ' + new_branch_name)
215-
run_git('checkout', '-b', new_branch_name, ORIGIN + '/' + MAIN_BRANCH)
216248

217-
print('Updating changelog')
218-
update_changelog(version)
249+
if args.mode == V1_MODE:
250+
# If we're performing a backport, start from the v1 branch
251+
print(f'Creating {new_branch_name} from the {ORIGIN}/v1 branch')
252+
run_git('checkout', '-b', new_branch_name, f'{ORIGIN}/v1')
253+
254+
# Revert the commit that we made as part of the last release that updated the version number and
255+
# changelog to refer to 1.x.x variants. This avoids merge conflicts in the changelog and
256+
# package.json files when we merge in the v2 branch.
257+
# This commit will not exist the first time we release the v1 branch from the v2 branch, so we
258+
# use `git log --grep` to conditionally revert the commit.
259+
print('Reverting the 1.x.x version number and changelog updates from the last release to avoid conflicts')
260+
v1_update_commits = run_git('log', '--grep', '^Update version and changelog for v', '--format=%H').split()
261+
262+
if len(v1_update_commits) > 0:
263+
print(f' Reverting {v1_update_commits[0]}')
264+
# Only revert the newest commit as older ones will already have been reverted in previous
265+
# releases.
266+
run_git('revert', v1_update_commits[0], '--no-edit')
267+
268+
# Also revert the "Update checked-in dependencies" commit created by Actions.
269+
update_dependencies_commit = run_git('log', '--grep', '^Update checked-in dependencies', '--format=%H').split()[0]
270+
print(f' Reverting {update_dependencies_commit}')
271+
run_git('revert', update_dependencies_commit, '--no-edit')
272+
273+
else:
274+
print(' Nothing to revert.')
275+
276+
print(f'Merging {ORIGIN}/{source_branch} into the release prep branch')
277+
run_git('merge', f'{ORIGIN}/{source_branch}', '--no-edit')
278+
279+
# Migrate the package version number from a v2 version number to a v1 version number
280+
print(f'Setting version number to {version}')
281+
subprocess.run(['npm', 'version', version, '--no-git-tag-version'])
282+
run_git('add', 'package.json', 'package-lock.json')
283+
284+
# Migrate the changelog notes from v2 version numbers to v1 version numbers
285+
print('Migrating changelog notes from v2 to v1')
286+
subprocess.run(['sed', '-i', 's/^## 2\./## 1./g', 'CHANGELOG.md'])
287+
288+
# Amend the commit generated by `npm version` to update the CHANGELOG
289+
run_git('add', 'CHANGELOG.md')
290+
run_git('commit', '-m', f'Update version and changelog for v{version}')
291+
else:
292+
# If we're performing a standard release, there won't be any new commits on the target branch,
293+
# as these will have already been merged back into the source branch. Therefore we can just
294+
# start from the source branch.
295+
run_git('checkout', '-b', new_branch_name, f'{ORIGIN}/{source_branch}')
296+
297+
print('Updating changelog')
298+
update_changelog(version)
219299

220-
# Create a commit that updates the CHANGELOG
221-
run_git('add', 'CHANGELOG.md')
222-
run_git('commit', '-m', version)
300+
# Create a commit that updates the CHANGELOG
301+
run_git('add', 'CHANGELOG.md')
302+
run_git('commit', '-m', f'Update changelog for v{version}')
223303

224304
run_git('push', ORIGIN, new_branch_name)
225305

226306
# Open a PR to update the branch
227-
open_pr(repo, commits, short_main_sha, new_branch_name)
307+
open_pr(
308+
repo,
309+
commits,
310+
source_branch_short_sha,
311+
new_branch_name,
312+
source_branch=source_branch,
313+
target_branch=target_branch,
314+
conductor=args.conductor,
315+
is_v2_release=args.mode == V2_MODE,
316+
labels=['Update dependencies'] if args.mode == V1_MODE else [],
317+
)
228318

229319
if __name__ == '__main__':
230320
main()

.github/workflows/post-release-mergeback.yml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ on:
1515
push:
1616
branches:
1717
- v1
18+
- v2
1819

1920
jobs:
2021
merge-back:
@@ -25,10 +26,13 @@ jobs:
2526
HEAD_BRANCH: "${{ github.head_ref || github.ref }}"
2627

2728
steps:
28-
- name: Dump GitHub Event context
29+
- name: Dump environment
30+
run: env
31+
32+
- name: Dump GitHub context
2933
env:
30-
GITHUB_EVENT_CONTEXT: "${{ toJson(github.event) }}"
31-
run: echo "$GITHUB_EVENT_CONTEXT"
34+
GITHUB_CONTEXT: '${{ toJson(github) }}'
35+
run: echo "$GITHUB_CONTEXT"
3236

3337
- uses: actions/checkout@v2
3438
- uses: actions/setup-node@v2
@@ -90,7 +94,7 @@ jobs:
9094
git push origin --follow-tags "$VERSION"
9195
9296
- name: Create mergeback branch
93-
if: steps.check.outputs.exists != 'true'
97+
if: steps.check.outputs.exists != 'true' && contains(github.ref, 'v2')
9498
env:
9599
VERSION: "${{ steps.getVersion.outputs.version }}"
96100
NEW_BRANCH: "${{ steps.getVersion.outputs.newBranch }}"
@@ -100,11 +104,13 @@ jobs:
100104
PR_TITLE="Mergeback $VERSION $HEAD_BRANCH into $BASE_BRANCH"
101105
PR_BODY="Updates version and changelog."
102106
107+
# Update the version number ready for the next release
108+
npm version patch --no-git-tag-version
109+
103110
# Update the changelog
104111
perl -i -pe 's/^/## \[UNRELEASED\]\n\nNo user facing changes.\n\n/ if($.==3)' CHANGELOG.md
105112
git add .
106113
git commit -m "Update changelog and version after $VERSION"
107-
npm version patch
108114
109115
git push origin "$NEW_BRANCH"
110116

.github/workflows/update-release-branch.yml

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
11
name: Update release branch
22
on:
3-
repository_dispatch:
4-
# Example of how to trigger this:
5-
# curl -H "Authorization: Bearer <token>" -X POST https://api.github.com/repos/github/codeql-action/dispatches -d '{"event_type":"update-release-branch"}'
6-
# Replace <token> with a personal access token from this page: https://github.com/settings/tokens
7-
types: [update-release-branch]
3+
# You can trigger this workflow via workflow dispatch to start a release.
4+
# This will open a PR to update the v2 release branch.
85
workflow_dispatch:
96

7+
# When the v2 release is complete, this workflow will open a PR to update the v1 release branch.
8+
push:
9+
branches:
10+
- v2
11+
1012
jobs:
1113
update:
1214
timeout-minutes: 45
1315
runs-on: ubuntu-latest
14-
if: ${{ github.repository == 'github/codeql-action' }}
16+
if: github.repository == 'github/codeql-action'
1517
steps:
18+
- name: Dump environment
19+
run: env
20+
21+
- name: Dump GitHub context
22+
env:
23+
GITHUB_CONTEXT: '${{ toJson(github) }}'
24+
run: echo "$GITHUB_CONTEXT"
25+
1626
- uses: actions/checkout@v2
1727
with:
1828
# Need full history so we calculate diffs
@@ -33,5 +43,20 @@ jobs:
3343
git config --global user.email "github-actions@github.com"
3444
git config --global user.name "github-actions[bot]"
3545
36-
- name: Update release branch
37-
run: python .github/update-release-branch.py ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }}
46+
- name: Update v2 release branch
47+
if: github.event_name == 'workflow_dispatch'
48+
run: |
49+
python .github/update-release-branch.py \
50+
--github-token ${{ secrets.GITHUB_TOKEN }} \
51+
--repository-nwo ${{ github.repository }} \
52+
--mode release-v2 \
53+
--conductor ${GITHUB_ACTOR}
54+
55+
- name: Update v1 release branch
56+
if: github.event_name == 'push'
57+
run: |
58+
python .github/update-release-branch.py \
59+
--github-token ${{ secrets.GITHUB_TOKEN }} \
60+
--repository-nwo ${{ github.repository }} \
61+
--mode release-v1 \
62+
--conductor ${GITHUB_ACTOR}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy