diff --git a/.github/update-release-branch.py b/.github/update-release-branch.py index 119bd984a3..9fb055b666 100644 --- a/.github/update-release-branch.py +++ b/.github/update-release-branch.py @@ -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. @@ -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 @@ -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] @@ -135,6 +153,28 @@ 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 ') @@ -142,10 +182,11 @@ def main(): 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 @@ -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 @@ -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 diff --git a/.github/workflows/post-release-mergeback.yml b/.github/workflows/post-release-mergeback.yml new file mode 100644 index 0000000000..05aad769de --- /dev/null +++ b/.github/workflows/post-release-mergeback.yml @@ -0,0 +1,124 @@ +# This workflow runs after a release of the action. +# It merges any changes from the release back into the +# 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: + 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." + + # 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" diff --git a/.github/workflows/update-release-branch.yml b/.github/workflows/update-release-branch.yml index a4989fb892..f6a8510848 100644 --- a/.github/workflows/update-release-branch.yml +++ b/.github/workflows/update-release-branch.yml @@ -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 }} diff --git a/package.json b/package.json index 438b752912..12a821152f 100644 --- a/package.json +++ b/package.json @@ -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 ." }, "ava": { "typescript": { diff --git a/runner/package-lock.json b/runner/package-lock.json index 6599262e05..4e545fd053 100644 --- a/runner/package-lock.json +++ b/runner/package-lock.json @@ -1,6 +1,6 @@ { "name": "codeql-runner", - "version": "0.0.0", + "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/runner/package.json b/runner/package.json index 66aee50baf..0acba5fbfb 100644 --- a/runner/package.json +++ b/runner/package.json @@ -1,6 +1,6 @@ { "name": "codeql-runner", - "version": "0.0.0", + "version": "1.0.0", "private": true, "description": "CodeQL runner", "scripts": {