From dcbc6c839d94919682b36a9c92ed2374dd409919 Mon Sep 17 00:00:00 2001 From: Yaron Kaikov Date: Tue, 11 Mar 2025 21:23:00 +0200 Subject: [PATCH] ./github/scripts/auto-backport.py: don't remove backport label when backport process has an error Today, when the `Fixes` prefix is missing or the developer is not a collaborator with `scylladbbot` we remove the backport labels to prevent the process from starting and notifying the developers. Developers are worried that removing these backport labels will cause us to forget we need to do these backports. @nyh suggested to add a `scylladbbot/backport_error` label instead Applied those changes, so when a `Fixes` prefix is missing we will add a `scylladbbot/backport_error` label and stop the process When a user doesn't accept the invite we will still open the PR but he will not be assigned and will not be able to edit the branch when we have conflicts Fixes: https://github.com/scylladb/scylla-pkg/issues/4898 Fixes: https://github.com/scylladb/scylla-pkg/issues/4897 --- .github/scripts/auto-backport.py | 50 +++++++++---------- .../workflows/add-label-when-promoted.yaml | 31 ++++++++---- 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/.github/scripts/auto-backport.py b/.github/scripts/auto-backport.py index 41ef71efde..6628cadaaa 100755 --- a/.github/scripts/auto-backport.py +++ b/.github/scripts/auto-backport.py @@ -29,10 +29,11 @@ def parse_args(): parser.add_argument('--commits', default=None, type=str, help='Range of promoted commits.') parser.add_argument('--pull-request', type=int, help='Pull request number to be backported') parser.add_argument('--head-commit', type=str, required=is_pull_request(), help='The HEAD of target branch after the pull request specified by --pull-request is merged') + parser.add_argument('--github-event', type=str, help='Get GitHub event type') return parser.parse_args() -def create_pull_request(repo, new_branch_name, base_branch_name, pr, backport_pr_title, commits, is_draft=False): +def create_pull_request(repo, new_branch_name, base_branch_name, pr, backport_pr_title, commits, is_draft, is_collaborator): pr_body = f'{pr.body}\n\n' for commit in commits: pr_body += f'- (cherry picked from commit {commit})\n\n' @@ -46,7 +47,8 @@ def create_pull_request(repo, new_branch_name, base_branch_name, pr, backport_pr draft=is_draft ) logging.info(f"Pull request created: {backport_pr.html_url}") - backport_pr.add_to_assignees(pr.user) + if is_collaborator: + backport_pr.add_to_assignees(pr.user) if is_draft: backport_pr.add_to_labels("conflicts") pr_comment = f"@{pr.user.login} - This PR was marked as draft because it has conflicts\n" @@ -92,18 +94,7 @@ def get_pr_commits(repo, pr, stable_branch, start_commit=None): return commits -def create_pr_comment_and_remove_label(pr, comment_body): - labels = pr.get_labels() - pattern = re.compile(r"backport/\d+\.\d+$") - for label in labels: - if pattern.match(label.name): - print(f"Removing label: {label.name}") - comment_body += f'- {label.name}\n' - pr.remove_from_labels(label) - pr.create_issue_comment(comment_body) - - -def backport(repo, pr, version, commits, backport_base_branch): +def backport(repo, pr, version, commits, backport_base_branch, is_collaborator): new_branch_name = f'backport/{pr.number}/to-{version}' backport_pr_title = f'[Backport {version}] {pr.title}' repo_url = f'https://scylladbbot:{github_token}@github.com/{repo.full_name}.git' @@ -123,7 +114,7 @@ def backport(repo, pr, version, commits, backport_base_branch): repo_local.git.cherry_pick('--continue') repo_local.git.push(fork_repo, new_branch_name, force=True) create_pull_request(repo, new_branch_name, backport_base_branch, pr, backport_pr_title, commits, - is_draft=is_draft) + is_draft, is_collaborator) except GitCommandError as e: logging.warning(f"GitCommandError: {e}") @@ -140,10 +131,6 @@ def with_github_keyword_prefix(repo, pr): break if not match: print(f'No valid close reference for {pr.number}') - comment = f':warning: @{pr.user.login} PR body does not contain a Fixes reference to an issue ' - comment += ' and can not be backported\n\n' - comment += 'The following labels were removed:\n' - create_pr_comment_and_remove_label(pr, comment) return False else: return True @@ -168,6 +155,7 @@ def main(): scylladbbot_repo = g.get_repo(fork_repo_name) closed_prs = [] start_commit = None + is_collaborator = True if args.commits: start_commit, end_commit = args.commits.split('..') @@ -192,21 +180,33 @@ def main(): if not backport_labels: print(f'no backport label: {pr.number}') continue - if args.commits and not with_github_keyword_prefix(repo, pr): + if not with_github_keyword_prefix(repo, pr) and args.github_event != 'unlabeled': + comment = f''':warning: @{pr.user.login} PR body or PR commits do not contain a Fixes reference to an issue and can not be backported + please update PR body with a valid ref to an issue. Then remove `scylladbbot/backport_error` label to re-trigger the backport process + ''' + pr.create_issue_comment(comment) + pr.add_to_labels("scylladbbot/backport_error") continue if not repo.private and not scylladbbot_repo.has_in_collaborators(pr.user.login): logging.info(f"Sending an invite to {pr.user.login} to become a collaborator to {scylladbbot_repo.full_name} ") scylladbbot_repo.add_to_collaborators(pr.user.login) - comment = f':warning: @{pr.user.login} you have been added as collaborator to scylladbbot fork ' - comment += f'Please check your inbox and approve the invitation, once it is done, please add the backport labels again\n' - create_pr_comment_and_remove_label(pr, comment) - continue + comment = f''':warning: @{pr.user.login} you have been added as collaborator to scylladbbot fork + Please check your inbox and approve the invitation, otherwise you will not be able to edit PR branch when needed + ''' + # When a pull request is pending for backport but its author is not yet a collaborator of "scylladbbot", + # we attach a "scylladbbot/backport_error" label to the PR. + # This prevents the workflow from proceeding with the backport process + # until the author has been granted proper permissions + # the author should remove the label manually to re-trigger the backport workflow. + pr.add_to_labels("scylladbbot/backport_error") + pr.create_issue_comment(comment) + is_collaborator = False commits = get_pr_commits(repo, pr, stable_branch, start_commit) logging.info(f"Found PR #{pr.number} with commit {commits} and the following labels: {backport_labels}") for backport_label in backport_labels: version = backport_label.replace('backport/', '') backport_base_branch = backport_label.replace('backport/', backport_branch) - backport(repo, pr, version, commits, backport_base_branch) + backport(repo, pr, version, commits, backport_base_branch, is_collaborator) if __name__ == "__main__": diff --git a/.github/workflows/add-label-when-promoted.yaml b/.github/workflows/add-label-when-promoted.yaml index 7afc1de3aa..7b7d3c1107 100644 --- a/.github/workflows/add-label-when-promoted.yaml +++ b/.github/workflows/add-label-when-promoted.yaml @@ -7,7 +7,7 @@ on: - branch-*.* - enterprise pull_request_target: - types: [labeled] + types: [labeled, unlabeled] branches: [master, next, enterprise] jobs: @@ -53,19 +53,28 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.AUTO_BACKPORT_TOKEN }} run: python .github/scripts/auto-backport.py --repo ${{ github.repository }} --base-branch ${{ github.ref }} --commits ${{ github.event.before }}..${{ github.sha }} - - name: Check if label starts with 'backport/' and contains digits + - name: Check if a valid backport label exists and no backport_error id: check_label run: | - label_name="${{ github.event.label.name }}" - if [[ "$label_name" =~ ^backport/[0-9]+\.[0-9]+$ ]]; then - echo "Label matches backport/X.X pattern." - echo "backport_label=true" >> $GITHUB_OUTPUT + labels_json='${{ toJson(github.event.pull_request.labels) }}' + echo "Checking labels: $(echo "$labels_json" | jq -r '.[].name')" + + # Check if a valid backport label exists + if echo "$labels_json" | jq -e 'any(.[] | .name; test("backport/[0-9]+\\.[0-9]+$"))' > /dev/null; then + # Ensure scylladbbot/backport_error is NOT present + if ! echo "$labels_json" | jq -e '.[] | select(.name == "scylladbbot/backport_error")' > /dev/null; then + echo "A matching backport label was found and no backport_error label exists." + echo "ready_for_backport=true" >> "$GITHUB_OUTPUT" + exit 0 + else + echo "The label 'scylladbbot/backport_error' is present, invalidating backport." + fi else - echo "Label does not match the required pattern." - echo "backport_label=false" >> $GITHUB_OUTPUT + echo "No matching backport label found." fi - - name: Run auto-backport.py when label was added - if: ${{ github.event_name == 'pull_request_target' && steps.check_label.outputs.backport_label == 'true' && github.event.pull_request.state == 'closed' }} + echo "ready_for_backport=false" >> "$GITHUB_OUTPUT" + - name: Run auto-backport.py when PR is closed + if: ${{ github.event_name == 'pull_request_target' && steps.check_label.outputs.ready_for_backport == 'true' && github.event.pull_request.state == 'closed' }} env: GITHUB_TOKEN: ${{ secrets.AUTO_BACKPORT_TOKEN }} - run: python .github/scripts/auto-backport.py --repo ${{ github.repository }} --base-branch ${{ github.ref }} --pull-request ${{ github.event.pull_request.number }} --head-commit ${{ github.event.pull_request.base.sha }} + run: python .github/scripts/auto-backport.py --repo ${{ github.repository }} --base-branch ${{ github.ref }} --pull-request ${{ github.event.pull_request.number }} --head-commit ${{ github.event.pull_request.base.sha }} --github-event ${{ github.event.action }}