diff --git a/.github/workflows/issue-release-workflow.yml b/.github/workflows/issue-release-workflow.yml --- a/.github/workflows/issue-release-workflow.yml +++ b/.github/workflows/issue-release-workflow.yml @@ -22,16 +22,40 @@ issues: types: - opened + - closed env: COMMENT_BODY: ${{ github.event.action == 'opened' && github.event.issue.body || github.event.comment.body }} jobs: + request-backport: + name: Request Backport + runs-on: ubuntu-20.04 + if: >- + (github.repository == 'llvm/llvm-project') && + github.event.action == 'closed' + + steps: + - name: Fetch LLVM sources + uses: actions/checkout@v2 + + - name: Setup Environment + run: | + pip install -r ./llvm/utils/git/requirements.txt + + - name: Create Issue + run: | + ./llvm/utils/git/github-automation.py \ + --token ${{ secrets.RELEASE_WORKFLOW_ISSUE_EDIT_SECRET }} \ + request-cherry-pick \ + --issue-number ${{ github.event.issue.number }} + backport-commits: name: Backport Commits runs-on: ubuntu-20.04 if: >- (github.repository == 'llvm/llvm-project') && + github.event.action != 'closed' && !startswith(github.event.comment.body, '') && contains(github.event.action == 'opened' && github.event.issue.body || github.event.comment.body, '/cherry-pick') steps: @@ -65,6 +89,7 @@ runs-on: ubuntu-20.04 if: >- (github.repository == 'llvm/llvm-project') && + github.event.action != 'closed' && !startswith(github.event.comment.body, '') && contains(github.event.comment.body, '/branch') diff --git a/llvm/utils/git/github-automation.py b/llvm/utils/git/github-automation.py --- a/llvm/utils/git/github-automation.py +++ b/llvm/utils/git/github-automation.py @@ -13,9 +13,25 @@ import github import os import re +import requests import sys from typing import * +def run_graphql_query(query: str, token: str) -> dict: + headers = { + 'Authorization' : 'bearer {}'.format(token), + } + request = requests.post( + url = 'https://api.github.com/graphql', + json = {"query" : query }, + headers = headers) + + if request.status_code == 200: + return request.json()['data'] + else: + raise Exception( + "Failed to run graphql query\nquery: {}\nerror: {}".format(query, request.json())) + class IssueSubscriber: @property @@ -250,6 +266,76 @@ print(sys.stdin.readlines()) return False +def create_cherry_pick_request_from_closed_issue(issue:int, repo_name:str, token:str): + """ + Request a cherry-pick of the commits that fixed `issue` by creating a new + issue and attaching it to the release milestone. + """ + + [owner, name] = repo_name.split('/') + + query = """ + query { + repository(owner: """ f'"{owner}"'', name: 'f'"{name}"'""") { + issue(number: """f'{issue}'""") { + title + timelineItems (itemTypes: [CLOSED_EVENT], last: 1) { + edges { + node { + __typename + ... on ClosedEvent { + closer { + ... on Commit { + author { + user { + login + } + } + committer { + user { + login + } + } + oid + } + } + } + } + } + } + } + } + }""" + + data = run_graphql_query(query, token) + title = data['repository']['issue']['title'] + event = data['repository']['issue']['timelineItems']['edges'] + authors = [] + committers = [] + commits = [] + for e in event: + closer = e['node']['closer'] + authors.append(closer['author']['user']['login']) + committers.append(closer['committer']['user']['login']) + commits.append(closer['oid']) + assignees = list(set(authors + committers)) + print(authors, committers, assignees, commits) + repo = github.Github(token).get_repo(repo_name) + # Find the most recent release milestone + milestone = None + for m in repo.get_milestones(state='open'): + if re.search('branch: release', m.description): + milestone = m + break + + message = '{} What do you think about backporting {}?\n\n/cherry-pick {}'.format(' '.join(['@' + login for login in assignees]), 'this' if len(commits) == 1 else 'these', ' '.join(commits)) + gh_issue = repo.create_issue(title='Cherry-pick fixes for #{}: {}'.format(issue, title), + assignees=assignees, + milestone=milestone, + body=message) + + + parser = argparse.ArgumentParser() parser.add_argument('--token', type=str, required=True, help='GitHub authentiation token') parser.add_argument('--repo', type=str, default=os.getenv('GITHUB_REPOSITORY', 'llvm/llvm-project'), @@ -267,9 +353,13 @@ help='GitHub authentication token to use for the repository where new branches will be pushed. Defaults to TOKEN.') release_workflow_parser.add_argument('--branch-repo', type=str, default='llvmbot/llvm-project', help='The name of the repo where new branches will be pushed (e.g. llvm/llvm-project)') -release_workflow_parser.add_argument('sub_command', type=str, choices=['print-release-branch', 'auto'], +release_workflow_parser.add_argument('sub_command', type=str, choices=['print-release-branch', 'auto', 'request-cherry-pick'], help='Print to stdout the name of the release branch ISSUE_NUMBER should be backported to') +request_cherry_pick_parser = subparsers.add_parser('request-cherry-pick') +request_cherry_pick_parser.add_argument('--issue-number', type=int, + help='Backport the commits that fixed ISSUE_NUMBER') + llvmbot_git_config_parser = subparsers.add_parser('setup-llvmbot-git', help='Set the default user and email for the git repo in LLVM_PROJECT_DIR to llvmbot') args = parser.parse_args() @@ -288,3 +378,8 @@ sys.exit(1) elif args.command == 'setup-llvmbot-git': setup_llvmbot_git() +elif args.command == 'request-cherry-pick': + if args.issue_number: + create_cherry_pick_request_from_closed_issue(args.issue_number, args.repo, args.token) + else: + raise Exception("Not implemented")