From a4746470d162934f1dfbd7d6c5b82f2f74b834c8 Mon Sep 17 00:00:00 2001 From: Andrew Savage Date: Thu, 15 Jun 2023 17:59:10 +0000 Subject: [PATCH 1/6] Create script to automatically update required gh checks --- tools/update_required_branch_checks.py | 45 ++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tools/update_required_branch_checks.py diff --git a/tools/update_required_branch_checks.py b/tools/update_required_branch_checks.py new file mode 100644 index 000000000000..71589691fa6c --- /dev/null +++ b/tools/update_required_branch_checks.py @@ -0,0 +1,45 @@ +# Copyright 2023 The Cobalt Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Updates the requires status checks for a branch.""" + +from github import Github + +BRANCH_NAME = 'main' + +YOUR_TOKEN = '' +g = Github(YOUR_TOKEN) + +repo = g.get_repo('youtube/cobalt') +latest_open_pr = repo.get_pulls( + state='open', sort='updated', base=BRANCH_NAME, direction='desc')[0] +latest_pr_commit = repo.get_commit(latest_open_pr.head) +check_runs = latest_pr_commit.get_check_runs() + + +# Filter out check runs that have '_on_device_' in the name +def should_include_run(check_run): + if '_on_device_' in check_run.name: + return False + return True + + +filtered_check_runs = [run for run in check_runs if should_include_run(run)] +check_names = [run.name for run in filtered_check_runs] + +branch = repo.get_branch(BRANCH_NAME) +protection = branch.get_protection() +check_names += protection.required_status_checks.contexts +checks = set(check_names) + +branch.edit_protection(contexts=check_names) From b097ba290e3915f9fdf96367820d3b9580d86084 Mon Sep 17 00:00:00 2001 From: Andrew Savage Date: Wed, 21 Jun 2023 15:55:05 +0000 Subject: [PATCH 2/6] Add loop across protected branches --- tools/update_required_branch_checks.py | 65 +++++++++++++++++++------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/tools/update_required_branch_checks.py b/tools/update_required_branch_checks.py index 71589691fa6c..3eb5d778b71b 100644 --- a/tools/update_required_branch_checks.py +++ b/tools/update_required_branch_checks.py @@ -14,32 +14,63 @@ """Updates the requires status checks for a branch.""" from github import Github +from typing import List -BRANCH_NAME = 'main' +YOUR_GITHUB_TOKEN = '' -YOUR_TOKEN = '' -g = Github(YOUR_TOKEN) +# Exclude rc_11 and COBALT_9 releases. +MINIMUM_LTS_RELEASE_NUMBER = 19 +LATEST_LTS_RELEASE_NUMBER = 24 -repo = g.get_repo('youtube/cobalt') -latest_open_pr = repo.get_pulls( - state='open', sort='updated', base=BRANCH_NAME, direction='desc')[0] -latest_pr_commit = repo.get_commit(latest_open_pr.head) -check_runs = latest_pr_commit.get_check_runs() +def get_protected_branches() -> List[str]: + branches = ['main'] + for i in range(MINIMUM_LTS_RELEASE_NUMBER, LATEST_LTS_RELEASE_NUMBER + 1): + branches.append(f'{i}.lts.1+') + return branches -# Filter out check runs that have '_on_device_' in the name -def should_include_run(check_run): + +def initialize_repo_connection(): + g = Github(YOUR_GITHUB_TOKEN) + return g.get_repo('youtube/cobalt') + + +def get_checks_for_branch(repo, branch: str): + latest_open_pr = repo.get_pulls( + state='open', sort='updated', base=branch, direction='desc')[0] + latest_pr_commit = repo.get_commit(latest_open_pr.head) + checks = latest_pr_commit.get_check_runs() + return checks + + +def should_include_run(check_run) -> bool: + # Filter out check runs that have '_on_device_' in the name. if '_on_device_' in check_run.name: return False + if check_run.name == 'feedback/copybara': + return False return True -filtered_check_runs = [run for run in check_runs if should_include_run(run)] -check_names = [run.name for run in filtered_check_runs] +def get_required_checks_for_branch(repo, branch: str) -> List[str]: + checks = get_checks_for_branch(repo, branch) + filtered_check_runs = [run for run in checks if should_include_run(run)] + check_names = [run.name for run in filtered_check_runs] + return check_names + + +def update_protection_for_branch(repo, branch: str, check_names: List[str]): + branch = repo.get_branch(branch) + branch.edit_protection(contexts=check_names) + + +def main(): + branches = get_protected_branches() + repo = initialize_repo_connection() + for branch in branches: + required_checks = get_required_checks_for_branch(repo, branch) + update_protection_for_branch(repo, branch, required_checks) -branch = repo.get_branch(BRANCH_NAME) -protection = branch.get_protection() -check_names += protection.required_status_checks.contexts -checks = set(check_names) -branch.edit_protection(contexts=check_names) +if __name__ == '__main__': + main() From 91cdaecfda1cbb40634a4ea041a010e2aa96e766 Mon Sep 17 00:00:00 2001 From: Andrew Savage Date: Wed, 21 Jun 2023 16:59:43 +0000 Subject: [PATCH 3/6] Add dry run option --- tools/update_required_branch_checks.py | 50 ++++++++++++++++++++------ 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/tools/update_required_branch_checks.py b/tools/update_required_branch_checks.py index 3eb5d778b71b..f65faea9ccfa 100644 --- a/tools/update_required_branch_checks.py +++ b/tools/update_required_branch_checks.py @@ -13,16 +13,31 @@ # limitations under the License. """Updates the requires status checks for a branch.""" +import argparse from github import Github from typing import List YOUR_GITHUB_TOKEN = '' +TARGET_REPO = 'youtube/cobalt' + +EXCLUDED_CHECK_PATTERNS = ['feedback/copybara', '_on_device_', r'${{'] + # Exclude rc_11 and COBALT_9 releases. MINIMUM_LTS_RELEASE_NUMBER = 19 LATEST_LTS_RELEASE_NUMBER = 24 +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument( + '--dry_run', + action='store_true', + default=False, + help='Only print protection updates.') + return parser.parse_args() + + def get_protected_branches() -> List[str]: branches = ['main'] for i in range(MINIMUM_LTS_RELEASE_NUMBER, LATEST_LTS_RELEASE_NUMBER + 1): @@ -32,23 +47,27 @@ def get_protected_branches() -> List[str]: def initialize_repo_connection(): g = Github(YOUR_GITHUB_TOKEN) - return g.get_repo('youtube/cobalt') + return g.get_repo(TARGET_REPO) def get_checks_for_branch(repo, branch: str): - latest_open_pr = repo.get_pulls( - state='open', sort='updated', base=branch, direction='desc')[0] - latest_pr_commit = repo.get_commit(latest_open_pr.head) + prs = repo.get_pulls( + state='open', sort='updated', base=branch, direction='desc') + try: + latest_pr = prs[0] + except IndexError: + prs = repo.get_pulls( + state='closed', sort='updated', base=branch, direction='desc') + latest_pr = prs[0] + latest_pr_commit = repo.get_commit(latest_pr.head.sha) checks = latest_pr_commit.get_check_runs() return checks def should_include_run(check_run) -> bool: - # Filter out check runs that have '_on_device_' in the name. - if '_on_device_' in check_run.name: - return False - if check_run.name == 'feedback/copybara': - return False + for pattern in EXCLUDED_CHECK_PATTERNS: + if pattern in check_run.name: + return False return True @@ -59,17 +78,28 @@ def get_required_checks_for_branch(repo, branch: str) -> List[str]: return check_names +def print_checks(branch: str, check_names: List[str]): + print(f'Checks for {branch}:') + for check_name in check_names: + print(check_name) + print() + + def update_protection_for_branch(repo, branch: str, check_names: List[str]): branch = repo.get_branch(branch) branch.edit_protection(contexts=check_names) def main(): + args = parse_args() branches = get_protected_branches() repo = initialize_repo_connection() for branch in branches: required_checks = get_required_checks_for_branch(repo, branch) - update_protection_for_branch(repo, branch, required_checks) + if args.dry_run: + print_checks(branch, required_checks) + else: + update_protection_for_branch(repo, branch, required_checks) if __name__ == '__main__': From 4338322649d9980c6e2963bd7aba3eee9b3474a2 Mon Sep 17 00:00:00 2001 From: Andrew Savage Date: Wed, 21 Jun 2023 17:11:39 +0000 Subject: [PATCH 4/6] Add branch flag --- tools/update_required_branch_checks.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/tools/update_required_branch_checks.py b/tools/update_required_branch_checks.py index f65faea9ccfa..1507b5423562 100644 --- a/tools/update_required_branch_checks.py +++ b/tools/update_required_branch_checks.py @@ -28,21 +28,32 @@ LATEST_LTS_RELEASE_NUMBER = 24 +def get_protected_branches() -> List[str]: + branches = ['main'] + for i in range(MINIMUM_LTS_RELEASE_NUMBER, LATEST_LTS_RELEASE_NUMBER + 1): + branches.append(f'{i}.lts.1+') + return branches + + def parse_args(): parser = argparse.ArgumentParser() + parser.add_argument( + '-b', + '--branch', + action='append', + help='Branch to update. Can be repeated to update multiple branches.' + ' Defaults to all protected branches.') parser.add_argument( '--dry_run', action='store_true', default=False, help='Only print protection updates.') - return parser.parse_args() + args = parser.parse_args() + if not args.branch: + args.branch = get_protected_branches() -def get_protected_branches() -> List[str]: - branches = ['main'] - for i in range(MINIMUM_LTS_RELEASE_NUMBER, LATEST_LTS_RELEASE_NUMBER + 1): - branches.append(f'{i}.lts.1+') - return branches + return args def initialize_repo_connection(): @@ -92,9 +103,8 @@ def update_protection_for_branch(repo, branch: str, check_names: List[str]): def main(): args = parse_args() - branches = get_protected_branches() repo = initialize_repo_connection() - for branch in branches: + for branch in args.branch: required_checks = get_required_checks_for_branch(repo, branch) if args.dry_run: print_checks(branch, required_checks) From ea7305422171cc7ec194fe1a58ba39a81eac2b5b Mon Sep 17 00:00:00 2001 From: Andrew Savage Date: Tue, 27 Jun 2023 16:18:24 +0000 Subject: [PATCH 5/6] Add more type annotations --- tools/update_required_branch_checks.py | 75 ++++++++++++++------------ 1 file changed, 42 insertions(+), 33 deletions(-) diff --git a/tools/update_required_branch_checks.py b/tools/update_required_branch_checks.py index 1507b5423562..d9689dabd8df 100644 --- a/tools/update_required_branch_checks.py +++ b/tools/update_required_branch_checks.py @@ -18,10 +18,17 @@ from typing import List YOUR_GITHUB_TOKEN = '' +assert YOUR_GITHUB_TOKEN != '', 'YOUR_GITHUB_TOKEN must be set.' TARGET_REPO = 'youtube/cobalt' -EXCLUDED_CHECK_PATTERNS = ['feedback/copybara', '_on_device_', r'${{'] +EXCLUDED_CHECK_PATTERNS = [ + 'feedback/copybara', + '_on_device_', + 'codecov', + # Excludes templated check names. + '${{' +] # Exclude rc_11 and COBALT_9 releases. MINIMUM_LTS_RELEASE_NUMBER = 19 @@ -35,41 +42,21 @@ def get_protected_branches() -> List[str]: return branches -def parse_args(): - parser = argparse.ArgumentParser() - parser.add_argument( - '-b', - '--branch', - action='append', - help='Branch to update. Can be repeated to update multiple branches.' - ' Defaults to all protected branches.') - parser.add_argument( - '--dry_run', - action='store_true', - default=False, - help='Only print protection updates.') - args = parser.parse_args() - - if not args.branch: - args.branch = get_protected_branches() - - return args - - def initialize_repo_connection(): g = Github(YOUR_GITHUB_TOKEN) return g.get_repo(TARGET_REPO) -def get_checks_for_branch(repo, branch: str): +def get_checks_for_branch(repo, branch: str) -> None: prs = repo.get_pulls( - state='open', sort='updated', base=branch, direction='desc') - try: - latest_pr = prs[0] - except IndexError: - prs = repo.get_pulls( - state='closed', sort='updated', base=branch, direction='desc') - latest_pr = prs[0] + state='closed', sort='updated', base=branch, direction='desc') + + latest_pr = None + for pr in prs: + if pr.merged: + latest_pr = pr + break + latest_pr_commit = repo.get_commit(latest_pr.head.sha) checks = latest_pr_commit.get_check_runs() return checks @@ -89,19 +76,41 @@ def get_required_checks_for_branch(repo, branch: str) -> List[str]: return check_names -def print_checks(branch: str, check_names: List[str]): +def print_checks(branch: str, check_names: List[str]) -> None: print(f'Checks for {branch}:') for check_name in check_names: print(check_name) print() -def update_protection_for_branch(repo, branch: str, check_names: List[str]): +def update_protection_for_branch(repo, branch: str, + check_names: List[str]) -> None: branch = repo.get_branch(branch) branch.edit_protection(contexts=check_names) -def main(): +def parse_args() -> None: + parser = argparse.ArgumentParser() + parser.add_argument( + '-b', + '--branch', + action='append', + help='Branch to update. Can be repeated to update multiple branches.' + ' Defaults to all protected branches.') + parser.add_argument( + '--dry_run', + action='store_true', + default=False, + help='Only print protection updates.') + args = parser.parse_args() + + if not args.branch: + args.branch = get_protected_branches() + + return args + + +def main() -> None: args = parse_args() repo = initialize_repo_connection() for branch in args.branch: From 6678584a7adec458eda6b6831a37ba1201766b6e Mon Sep 17 00:00:00 2001 From: Andrew Savage Date: Wed, 28 Jun 2023 00:06:49 +0000 Subject: [PATCH 6/6] Exclude cherry pick checks --- tools/update_required_branch_checks.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/update_required_branch_checks.py b/tools/update_required_branch_checks.py index d9689dabd8df..d3c04ea294e3 100644 --- a/tools/update_required_branch_checks.py +++ b/tools/update_required_branch_checks.py @@ -26,6 +26,8 @@ 'feedback/copybara', '_on_device_', 'codecov', + 'prepare_branch_list', + 'cherry_pick', # Excludes templated check names. '${{' ]