Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add release and mergeback workflows #510

Merged
merged 3 commits into from
May 27, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 64 additions & 15 deletions .github/update-release-branch.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
import requests
import subprocess
import sys
import json
import datetime
import os

EMPTY_CHANGELOG = """
# CodeQL Action and CodeQL Runner Changelog

## [UNRELEASED]

"""

# The branch being merged from.
# This is the one that contains day-to-day development work.
Expand Down Expand Up @@ -49,32 +59,40 @@ def open_pr(repo, all_commits, short_main_sha, branch_name):
commits_without_pull_requests = sorted(commits_without_pull_requests, key=lambda c: c.commit.author.date)

# Start constructing the body text
body = 'Merging ' + short_main_sha + ' into ' + LATEST_RELEASE_BRANCH
body = []
body.append('Merging ' + short_main_sha + ' into ' + LATEST_RELEASE_BRANCH)

conductor = get_conductor(repo, pull_requests, commits_without_pull_requests)
body += '\n\nConductor for this PR is @' + conductor
body.append('')
body.append('Conductor for this PR is @' + conductor)

# List all PRs merged
if len(pull_requests) > 0:
body += '\n\nContains the following pull requests:'
body.append('')
body.append('Contains the following pull requests:')
for pr in pull_requests:
merger = get_merger_of_pr(repo, pr)
body += '\n- #' + str(pr.number)
body += ' - ' + pr.title
body += ' (@' + merger + ')'
body.append('- #' + str(pr.number) + ' - ' + pr.title +' (@' + merger + ')')

# List all commits not part of a PR
if len(commits_without_pull_requests) > 0:
body += '\n\nContains the following commits not from a pull request:'
body.append('')
body.append('Contains the following commits not from a pull request:')
for commit in commits_without_pull_requests:
body += '\n- ' + commit.sha
body += ' - ' + get_truncated_commit_message(commit)
body += ' (@' + commit.author.login + ')'
body.append('- ' + commit.sha + ' - ' + get_truncated_commit_message(commit) + ' (@' + commit.author.login + ')')

body.append('')
body.append('Please review the following:')
body.append(' - [ ] The CHANGELOG displays the correct version and date.')
body.append(' - [ ] The CHANGELOG includes all relevant, user-facing changes since the last release.')
body.append(' - [ ] There are no unexpected commits being merged into the ' + LATEST_RELEASE_BRANCH + ' branch.')
body.append(' - [ ] The docs team is aware of any documentation changes that need to be released.')
body.append(' - [ ] The mergeback PR is merged back into ' + MAIN_BRANCH + ' after this PR is merged.')

title = 'Merge ' + MAIN_BRANCH + ' into ' + LATEST_RELEASE_BRANCH

# Create the pull request
pr = repo.create_pull(title=title, body=body, head=branch_name, base=LATEST_RELEASE_BRANCH)
pr = repo.create_pull(title=title, body='\n'.join(body), head=branch_name, base=LATEST_RELEASE_BRANCH)
print('Created PR #' + str(pr.number))

# Assign the conductor
Expand All @@ -95,7 +113,7 @@ def get_conductor(repo, pull_requests, other_commits):
# This will not include any commits that exist on the release branch
# that aren't on main.
def get_commit_difference(repo):
commits = run_git('log', '--pretty=format:%H', ORIGIN + '/' + LATEST_RELEASE_BRANCH + '..' + MAIN_BRANCH).strip().split('\n')
commits = run_git('log', '--pretty=format:%H', ORIGIN + '/' + LATEST_RELEASE_BRANCH + '..' + ORIGIN + '/' + MAIN_BRANCH).strip().split('\n')

# Convert to full-fledged commit objects
commits = [repo.get_commit(c) for c in commits]
Expand Down Expand Up @@ -135,17 +153,40 @@ def get_pr_for_commit(repo, commit):
def get_merger_of_pr(repo, pr):
return repo.get_commit(pr.merge_commit_sha).author.login

def get_current_version():
with open('package.json', 'r') as f:
return json.load(f)['version']

def get_today_string():
today = datetime.datetime.today()
return '{:%d %b %Y}'.format(today)

def update_changelog(version):
if (os.path.exists('CHANGELOG.md')):
content = ''
with open('CHANGELOG.md', 'r') as f:
content = f.read()
else:
content = EMPTY_CHANGELOG

newContent = content.replace('[UNRELEASED]', version + ' - ' + get_today_string(), 1)

with open('CHANGELOG.md', 'w') as f:
f.write(newContent)


def main():
if len(sys.argv) != 3:
raise Exception('Usage: update-release.branch.py <github token> <repository nwo>')
github_token = sys.argv[1]
repository_nwo = sys.argv[2]

repo = Github(github_token).get_repo(repository_nwo)
version = get_current_version()

# Print what we intend to go
print('Considering difference between ' + MAIN_BRANCH + ' and ' + LATEST_RELEASE_BRANCH)
short_main_sha = run_git('rev-parse', '--short', MAIN_BRANCH).strip()
short_main_sha = run_git('rev-parse', '--short', ORIGIN + '/' + MAIN_BRANCH).strip()
print('Current head of ' + MAIN_BRANCH + ' is ' + short_main_sha)

# See if there are any commits to merge in
Expand All @@ -157,7 +198,7 @@ def main():
# The branch name is based off of the name of branch being merged into
# and the SHA of the branch being merged from. Thus if the branch already
# exists we can assume we don't need to recreate it.
new_branch_name = 'update-' + LATEST_RELEASE_BRANCH + '-' + short_main_sha
new_branch_name = 'update-v' + version + '-' + short_main_sha
print('Branch name is ' + new_branch_name)

# Check if the branch already exists. If so we can abort as this script
Expand All @@ -168,7 +209,15 @@ def main():

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

print('Updating changelog')
update_changelog(version)

# Create a commit that updates the CHANGELOG
run_git('add', 'CHANGELOG.md')
run_git('commit', '-m', version)

run_git('push', ORIGIN, new_branch_name)

# Open a PR to update the branch
Expand Down
124 changes: 124 additions & 0 deletions .github/workflows/post-release-mergeback.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# This workflow runs after a release of the action.
# It merges any changes from the release back into the
aeisenberg marked this conversation as resolved.
Show resolved Hide resolved
# main branch. Typically, this is just a single commit
# that updates the changelog.
name: Tag release and merge back

on:
workflow_dispatch:
inputs:
baseBranch:
description: 'The base branch to merge into'
default: main
required: false

push:
branches:
- v1

pull_request:
paths:
- .github/workflows/post-release-mergeback.yml

jobs:
merge-back:
runs-on: ubuntu-latest
if: github.repository == 'github/codeql-action'
env:
aeisenberg marked this conversation as resolved.
Show resolved Hide resolved
BASE_BRANCH: "${{ github.event.inputs.baseBranch || 'main' }}"
HEAD_BRANCH: "${{ github.head_ref || github.ref }}"

steps:
- name: Dump GitHub Event context
env:
GITHUB_EVENT_CONTEXT: "${{ toJson(github.event) }}"
run: echo "$GITHUB_EVENT_CONTEXT"

- uses: actions/checkout@v2
- uses: actions/setup-node@v2

- name: Update git config
run: |
git config --global user.email "github-actions@github.com"
git config --global user.name "github-actions[bot]"

- name: Get version and new branch
id: getVersion
run: |
VERSION="v$(jq '.version' -r 'package.json')"
SHORT_SHA="${GITHUB_SHA:0:8}"
echo "::set-output name=version::$VERSION"
NEW_BRANCH="mergeback/${VERSION}-to-${BASE_BRANCH}-${SHORT_SHA}"
echo "::set-output name=newBranch::$NEW_BRANCH"


- name: Dump branches
env:
NEW_BRANCH: "${{ steps.getVersion.outputs.newBranch }}"
run: |
echo "BASE_BRANCH $BASE_BRANCH"
echo "HEAD_BRANCH $HEAD_BRANCH"
echo "NEW_BRANCH $NEW_BRANCH"

- name: Create mergeback branch
env:
NEW_BRANCH: "${{ steps.getVersion.outputs.newBranch }}"
run: |
git checkout -b "$NEW_BRANCH"

- name: Check for tag
id: check
env:
VERSION: "${{ steps.getVersion.outputs.version }}"
run: |
set +e # don't fail on an errored command
git ls-remote --tags origin | grep "$VERSION"
EXISTS="$?"
if [ "$EXISTS" -ne 0 ]; then
echo "::set-output name=exists::true"
echo "Tag $TAG exists. Not going to re-release."
fi

# we didn't tag the release during the update-release-branch workflow because the
# commit that actually makes it to the release branch is a merge commit,
# and not yet known during the first workflow. We tag now because we know the correct commit.
- name: Tag release
if: steps.check.outputs.exists == 'true'
env:
VERSION: ${{ steps.getVersion.outputs.version }}
run: |
git tag -a "$VERSION" -m "$VERSION"
git push origin --follow-tags "$VERSION"

- name: Create mergeback branch
if: steps.check.outputs.exists == 'true'
env:
VERSION: "${{ steps.getVersion.outputs.version }}"
NEW_BRANCH: "${{ steps.getVersion.outputs.newBranch }}"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
run: |
set -exu
PR_TITLE="Mergeback $VERSION $HEAD_BRANCH into $BASE_BRANCH"
PR_BODY="Updates version and changelog."
aeisenberg marked this conversation as resolved.
Show resolved Hide resolved

# Update the changelog
perl -i -pe 's/^/## \[UNRELEASED\]\n\n/ if($.==3)' CHANGELOG.md
git add .
git commit -m "Update changelog and version after $VERSION"
npm version patch

# when running this workflow on a PR, this is just a test.
# so put into draft mode.
if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then
DRAFT="--draft"
else
DRAFT=""
fi

git push origin "$NEW_BRANCH"
gh pr create \
--head "$NEW_BRANCH" \
--base "$BASE_BRANCH" \
--title "$PR_TITLE" \
--body "$PR_BODY" \
"$DRAFT"
adityasharad marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 6 additions & 1 deletion .github/workflows/update-release-branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,17 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.5
python-version: 3.8

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install PyGithub==1.51 requests

- name: Update git config
run: |
git config --global user.email "github-actions@github.com"
git config --global user.name "github-actions[bot]"

- name: Update release branch
run: python .github/update-release-branch.py ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"test-debug": "ava src/** --serial --verbose --timeout=20m",
"lint": "eslint --report-unused-disable-directives --max-warnings=0 . --ext .js,.ts",
"lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --ext .js,.ts --fix",
"removeNPMAbsolutePaths": "removeNPMAbsolutePaths . --force"
"removeNPMAbsolutePaths": "removeNPMAbsolutePaths . --force",
"version": "cd runner && npm version patch && git add ."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only for the runner? Should it also update the Action's package.json?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not exactly. This script ensures that when npm version ... is called, the version script runs and that also triggers the npm version on the runner. So, both will be updated in tandem.

See npm help version

If preversion, version, or postversion are in the scripts property of the package.json, they will be executed as part of running npm version.
...
4. Run the version script. These scripts have access to the new version in package.json...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see. So calling npm version at the root will then invoke this script, which bumps the version in the runner. Makes sense.

Consider avoiding the git add . here, and keeping that in the Actions workflow instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By running git add ., this ensures that the version changes to the runner get included in the same commit as the version changes to the containing action. I could run git commit --amend in the workflow, but that seems messier if someone is running the npm version command manually.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Do you know which file it will touch? Perhaps just add the package.json file then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

package.json and package-lock.json. It's pretty safe running add . since the initial npm version will fail if the working directory is not clean. I have a slight preference for keeping the . since this will be more future proof if npm version ever does start touching different files.

Also note that the invocation of npm version patch in the runner directory doesn't create a commit since commits are only created if the package.json is in the root of the git repo.

},
"ava": {
"typescript": {
Expand Down
2 changes: 1 addition & 1 deletion runner/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion runner/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codeql-runner",
"version": "0.0.0",
"version": "1.0.0",
"private": true,
"description": "CodeQL runner",
"scripts": {
Expand Down