Compare commits

...

1 Commits

Author SHA1 Message Date
Yaron Kaikov
dcbc6c839d ./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
2025-03-18 13:58:59 +02:00
2 changed files with 45 additions and 36 deletions

View File

@@ -29,10 +29,11 @@ def parse_args():
parser.add_argument('--commits', default=None, type=str, help='Range of promoted commits.') 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('--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('--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() 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' pr_body = f'{pr.body}\n\n'
for commit in commits: for commit in commits:
pr_body += f'- (cherry picked from commit {commit})\n\n' 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 draft=is_draft
) )
logging.info(f"Pull request created: {backport_pr.html_url}") 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: if is_draft:
backport_pr.add_to_labels("conflicts") backport_pr.add_to_labels("conflicts")
pr_comment = f"@{pr.user.login} - This PR was marked as draft because it has conflicts\n" 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 return commits
def create_pr_comment_and_remove_label(pr, comment_body): def backport(repo, pr, version, commits, backport_base_branch, is_collaborator):
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):
new_branch_name = f'backport/{pr.number}/to-{version}' new_branch_name = f'backport/{pr.number}/to-{version}'
backport_pr_title = f'[Backport {version}] {pr.title}' backport_pr_title = f'[Backport {version}] {pr.title}'
repo_url = f'https://scylladbbot:{github_token}@github.com/{repo.full_name}.git' 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.cherry_pick('--continue')
repo_local.git.push(fork_repo, new_branch_name, force=True) 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, 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: except GitCommandError as e:
logging.warning(f"GitCommandError: {e}") logging.warning(f"GitCommandError: {e}")
@@ -140,10 +131,6 @@ def with_github_keyword_prefix(repo, pr):
break break
if not match: if not match:
print(f'No valid close reference for {pr.number}') 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 return False
else: else:
return True return True
@@ -168,6 +155,7 @@ def main():
scylladbbot_repo = g.get_repo(fork_repo_name) scylladbbot_repo = g.get_repo(fork_repo_name)
closed_prs = [] closed_prs = []
start_commit = None start_commit = None
is_collaborator = True
if args.commits: if args.commits:
start_commit, end_commit = args.commits.split('..') start_commit, end_commit = args.commits.split('..')
@@ -192,21 +180,33 @@ def main():
if not backport_labels: if not backport_labels:
print(f'no backport label: {pr.number}') print(f'no backport label: {pr.number}')
continue 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 continue
if not repo.private and not scylladbbot_repo.has_in_collaborators(pr.user.login): 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} ") 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) 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''':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' Please check your inbox and approve the invitation, otherwise you will not be able to edit PR branch when needed
create_pr_comment_and_remove_label(pr, comment) '''
continue # 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) 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}") logging.info(f"Found PR #{pr.number} with commit {commits} and the following labels: {backport_labels}")
for backport_label in backport_labels: for backport_label in backport_labels:
version = backport_label.replace('backport/', '') version = backport_label.replace('backport/', '')
backport_base_branch = backport_label.replace('backport/', backport_branch) 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__": if __name__ == "__main__":

View File

@@ -7,7 +7,7 @@ on:
- branch-*.* - branch-*.*
- enterprise - enterprise
pull_request_target: pull_request_target:
types: [labeled] types: [labeled, unlabeled]
branches: [master, next, enterprise] branches: [master, next, enterprise]
jobs: jobs:
@@ -53,19 +53,28 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.AUTO_BACKPORT_TOKEN }} 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 }} 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 id: check_label
run: | run: |
label_name="${{ github.event.label.name }}" labels_json='${{ toJson(github.event.pull_request.labels) }}'
if [[ "$label_name" =~ ^backport/[0-9]+\.[0-9]+$ ]]; then echo "Checking labels: $(echo "$labels_json" | jq -r '.[].name')"
echo "Label matches backport/X.X pattern."
echo "backport_label=true" >> $GITHUB_OUTPUT # 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 else
echo "Label does not match the required pattern." echo "No matching backport label found."
echo "backport_label=false" >> $GITHUB_OUTPUT
fi fi
- name: Run auto-backport.py when label was added echo "ready_for_backport=false" >> "$GITHUB_OUTPUT"
if: ${{ github.event_name == 'pull_request_target' && steps.check_label.outputs.backport_label == 'true' && github.event.pull_request.state == 'closed' }} - 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: env:
GITHUB_TOKEN: ${{ secrets.AUTO_BACKPORT_TOKEN }} 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 }}