From 6920a5f0b780213fb341308558204f2c74c8cbfa Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Mon, 1 Sep 2025 13:06:36 +0200 Subject: [PATCH 1/2] Move first workflows from napari/napari --- .github/dependabot.yml | 16 ++++ .github/workflows/edit_pr_description.yml | 36 ++++++++ .github/workflows/remove_ready_to_merge.yml | 34 ++++++++ scripts/remove_html_comments_from_pr.py | 97 +++++++++++++++++++++ 4 files changed, 183 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/edit_pr_description.yml create mode 100644 .github/workflows/remove_ready_to_merge.yml create mode 100644 scripts/remove_html_comments_from_pr.py diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b0b9e82 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + commit-message: + prefix: "ci(dependabot):" + labels: + - "maintenance" + groups: + actions: + patterns: + - "*" diff --git a/.github/workflows/edit_pr_description.yml b/.github/workflows/edit_pr_description.yml new file mode 100644 index 0000000..b6f9acf --- /dev/null +++ b/.github/workflows/edit_pr_description.yml @@ -0,0 +1,36 @@ +name: Clean up PR description + +on: + # see https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target + pull_request_target: + types: + - opened + - synchronize + - reopened + - edited + workflow_call: +# see https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs +permissions: + pull-requests: write + +jobs: + check_labels: + name: Remove html comments + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: napari/napari + # install python and requests + - name: Install the latest version of uv + uses: astral-sh/setup-uv@v6 + with: + version: "latest" + - name: Remove html comments + env: + GH_PR_NUMBER: ${{ github.event.number }} + GH_REPO_URL: ${{ github.event.repository.url}} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + run: | + uv run --script scripts/remove_html_comments_from_pr.py diff --git a/.github/workflows/remove_ready_to_merge.yml b/.github/workflows/remove_ready_to_merge.yml new file mode 100644 index 0000000..f7b800d --- /dev/null +++ b/.github/workflows/remove_ready_to_merge.yml @@ -0,0 +1,34 @@ +name: Remove "ready to merge" label + +on: + pull_request_target: + types: [closed] + workflow_call: + + +permissions: + pull-requests: write + +jobs: + remove_label: + runs-on: ubuntu-latest + steps: + - name: Remove label + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + const { owner, repo, number: pull_number } = context.issue; + const { data: pullRequest } = await github.rest.pulls.get({ owner, repo, pull_number }); + if (!pullRequest.merged) { + console.log("Pull request not merged, skipping."); + return; + } + const { data: labels } = await github.rest.issues.listLabelsOnIssue({ owner, repo, issue_number: pull_number }); + const labelToRemove = labels.find(label => label.name === "ready to merge"); + if (!labelToRemove) { + console.log("Label not found on pull request, skipping."); + return; + } + await github.rest.issues.removeLabel({ owner, repo, issue_number: pull_number, name: "ready to merge" }); + console.log("Label removed."); diff --git a/scripts/remove_html_comments_from_pr.py b/scripts/remove_html_comments_from_pr.py new file mode 100644 index 0000000..b32e40a --- /dev/null +++ b/scripts/remove_html_comments_from_pr.py @@ -0,0 +1,97 @@ + +# /// script +# dependencies = [ +# "requests" +# ] +# requires-python = ">=3.11" +# /// +""" +Edit pull request description to remove HTML comments + +We might want to remove section with markdown task lists that are completely empty +""" + +import re +import sys +from os import environ + +import requests + +REPO = 'napari/napari' + + +def remove_html_comments(text): + # Regular expression to remove HTML comments + # [^\S\r\n] is whitespace but not new line + html_comment_pattern = r'[^\S\r\n]*[^\S\r\n]*\s*' + return re.sub(html_comment_pattern, '\n', text, flags=re.DOTALL) + + +def edit_pull_request_description(repo, pull_request_number, access_token): + # GitHub API base URL + base_url = 'https://api.github.com' + + # Prepare the headers with the access token + headers = {'Authorization': f'token {access_token}'} + + # Get the current pull request description + pr_url = f'{base_url}/repos/{repo}/pulls/{pull_request_number}' + response = requests.get(pr_url, headers=headers) + response.raise_for_status() + response_json = response.json() + current_description = response_json['body'] + + # Remove HTML comments from the description + edited_description = remove_html_comments(current_description) + + if edited_description == current_description: + print('No HTML comments found in the pull request description') + return + + # Update the pull request description + update_pr_url = f'{base_url}/repos/{repo}/pulls/{pull_request_number}' + payload = {'body': edited_description} + response = requests.patch(update_pr_url, json=payload, headers=headers) + response.raise_for_status() + + if response.status_code == 200: + print( + f'Pull request #{pull_request_number} description has been updated successfully!' + ) + else: + print( + f'Failed to update pull request description. Status code: {response.status_code}' + ) + + +if __name__ == '__main__': + print('Will inspect PR description to remove html comments.') + + # note that the env between pull_request and pull_request_target are different + # and the github documentation is incorrect (or at least misleading) + # and likely varies between pull request intra-repository and inter-repository + # thus we log many things to try to understand what is going on in case of failure. + # among other: + # - github.event.repository.name is not the full slug, but just the name + # - github.event.repository.org is empty if the repo is a normal user. + + repository_url = environ.get('GH_REPO_URL') + print(f'Current repository is {repository_url}') + repository_parts = repository_url.split('/')[-2:] + + slug = '/'.join(repository_parts) + print(f'Current slug is {slug}') + if slug != REPO: + print('Not on main repo, aborting with success') + sys.exit(0) + + # get current PR number from github actions + number = environ.get('GH_PR_NUMBER') + print(f'Current PR number is {number}') + + access_token = environ.get('GH_TOKEN') + if access_token is None: + print('No access token found in the environment variables') + # we still don't want fail status + sys.exit(0) + edit_pull_request_description(slug, number, access_token) From de85c12e75ba7c07e9f7a6ec6d1596bd1e3f0063 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Fri, 26 Sep 2025 17:32:33 +0200 Subject: [PATCH 2/2] modernize ready to merge removal workflow --- .github/workflows/remove_ready_to_merge.yml | 44 ++++++++++++--------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/.github/workflows/remove_ready_to_merge.yml b/.github/workflows/remove_ready_to_merge.yml index f7b800d..3ff553e 100644 --- a/.github/workflows/remove_ready_to_merge.yml +++ b/.github/workflows/remove_ready_to_merge.yml @@ -1,11 +1,7 @@ name: Remove "ready to merge" label on: - pull_request_target: - types: [closed] workflow_call: - - permissions: pull-requests: write @@ -16,19 +12,31 @@ jobs: - name: Remove label uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 with: - github-token: ${{secrets.GITHUB_TOKEN}} + github-token: ${{ secrets.GITHUB_TOKEN }} script: | - const { owner, repo, number: pull_number } = context.issue; - const { data: pullRequest } = await github.rest.pulls.get({ owner, repo, pull_number }); - if (!pullRequest.merged) { - console.log("Pull request not merged, skipping."); - return; - } - const { data: labels } = await github.rest.issues.listLabelsOnIssue({ owner, repo, issue_number: pull_number }); - const labelToRemove = labels.find(label => label.name === "ready to merge"); - if (!labelToRemove) { - console.log("Label not found on pull request, skipping."); - return; + const { data: issues } = await github.rest.issues.listForRepo({ + ...context.repo, + state: 'closed', + per_page: 100, + sort: 'updated', + direction: 'desc', + labels: 'ready to merge', + }); + if (issues.length > 0) { + console.log('Found the following closed items with the "ready to merge" label:'); + for (const issue of issues) { + console.log(`- #${issue.number}: ${issue.title}`); + try { + await github.rest.issues.removeLabel({ + ...context.repo, + issue_number: issue.number, + name: 'ready to merge' + }); + console.log(` > Removed 'ready to merge' label from #${issue.number}`); + } catch (error) { + console.error(` > Failed to remove label from #${issue.number}: ${error.message}`); + } + } + } else { + console.log('No closed items found with the "ready to merge" label.'); } - await github.rest.issues.removeLabel({ owner, repo, issue_number: pull_number, name: "ready to merge" }); - console.log("Label removed.");