From c36930c345f6f2b53349d4919887d08d158547b7 Mon Sep 17 00:00:00 2001 From: mhucka Date: Fri, 2 Jan 2026 19:20:24 +0000 Subject: [PATCH 1/5] Fix incorrect path in git checkout --- .github/workflows/dependabot-pr-cleaner.yaml | 74 ++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 .github/workflows/dependabot-pr-cleaner.yaml diff --git a/.github/workflows/dependabot-pr-cleaner.yaml b/.github/workflows/dependabot-pr-cleaner.yaml new file mode 100644 index 00000000..b0548b15 --- /dev/null +++ b/.github/workflows/dependabot-pr-cleaner.yaml @@ -0,0 +1,74 @@ +# Copyright 2026 Google LLC +# +# 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 +# +# https://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. + +name: Dependabot PR cleaner +run-name: >- + Clean up description of PR ${{github.event.pull_request.number}} on + ${{github.ref_name}} by @${{github.actor}} + +on: + pull_request_target: + types: + - opened + - synchronize + - reopened + + workflow_dispatch: + inputs: + pr-number: + description: 'The PR number of the PR to clean:' + required: true + debug: + description: 'Run with debugging options' + type: boolean + default: true + +# Declare default workflow permissions as read only. +permissions: read-all + +jobs: + clean-pr-description: + if: >- + ${{github.actor == 'dependabot[bot]' && + github.repository_owner == 'quantumlib'}} + name: Clean PR description + runs-on: ubuntu-slim + timeout-minutes: 5 + permissions: + contents: read + pull-requests: write + steps: + - name: Check out a copy of the git repository + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + sparse-checkout: | + ./dev_tools/ci/dependabot_pr_description_cleaner.py + + - name: Set up Python + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v5 + with: + cache: pip + + - name: Install dependencies + run: pip install html2text + + - name: Clean the PR description + env: + GH_REPO: ${{github.repository}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} + PR_NUMBER: ${{inputs.pr-number || github.event.pull_request.number}} + run: | + gh pr view ${{env.PR_NUMBER}} --json body --jq .body > body.txt + python3 dev_tools/ci/dependabot_pr_description_cleaner.py body.txt + gh pr edit ${{env.PR_NUMBER}} --body-file body.txt From ea62bcc1bc94f552dce9167204b0c6ed8d021660 Mon Sep 17 00:00:00 2001 From: mhucka Date: Fri, 2 Jan 2026 19:22:36 +0000 Subject: [PATCH 2/5] Add handling of debug flag --- .github/workflows/dependabot-pr-cleaner.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/dependabot-pr-cleaner.yaml b/.github/workflows/dependabot-pr-cleaner.yaml index b0548b15..d95f0b2d 100644 --- a/.github/workflows/dependabot-pr-cleaner.yaml +++ b/.github/workflows/dependabot-pr-cleaner.yaml @@ -68,6 +68,7 @@ jobs: GH_REPO: ${{github.repository}} GH_TOKEN: ${{secrets.GITHUB_TOKEN}} PR_NUMBER: ${{inputs.pr-number || github.event.pull_request.number}} + SHELLOPTS: ${{inputs.debug && 'xtrace' || '' }} run: | gh pr view ${{env.PR_NUMBER}} --json body --jq .body > body.txt python3 dev_tools/ci/dependabot_pr_description_cleaner.py body.txt From 2663b470fb932bed088fb30e35f3ac0a2361c0d6 Mon Sep 17 00:00:00 2001 From: mhucka Date: Fri, 2 Jan 2026 19:25:15 +0000 Subject: [PATCH 3/5] Add script to clean up Dependabot PR descriptions This adds a simple Python program to clean up the Dependabot description bodies to remove unwanted sections (like the list of commands) and convert the HTML body to Markdown. Pytest test cases are also provided. --- .../ci/dependabot_pr_description_cleaner.py | 79 ++++ .../dependabot_pr_description_cleaner_test.py | 342 ++++++++++++++++++ 2 files changed, 421 insertions(+) create mode 100644 dev_tools/ci/dependabot_pr_description_cleaner.py create mode 100644 dev_tools/ci/dependabot_pr_description_cleaner_test.py diff --git a/dev_tools/ci/dependabot_pr_description_cleaner.py b/dev_tools/ci/dependabot_pr_description_cleaner.py new file mode 100644 index 00000000..ebb78f32 --- /dev/null +++ b/dev_tools/ci/dependabot_pr_description_cleaner.py @@ -0,0 +1,79 @@ +# Copyright 2026 Google LLC +# +# 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 +# +# https://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. + +"""Convert and streamline description bodies of Dependabot PRs.""" + +import os +import re +import sys + +import html2text + + +def process_text(content): + """Take PR description body and clean it. + + Cleaning steps applied: + * Remove the section "Commits" + * Convert HTML to Markdown + * Remove the section "Dependabot commands and options" + """ + + # Pattern: match the start of the details block for Commits, lazy match + # until the closing details tag, and match the following br tag if present. + content = re.sub( + r'
\s*Commits.*?
\s*()?', + '', + content, + flags=re.DOTALL | re.IGNORECASE + ) + + h = html2text.HTML2Text() + h.body_width = 0 + markdown_content = h.handle(content) + + target_phrase = "Dependabot commands and options" + if target_phrase in markdown_content: + # Split at the last occurrence of the phrase. + parts = markdown_content.rsplit(target_phrase, 1) + pre_text = parts[0] + pre_text = pre_text.rstrip() + # Remove the marker if present at the end. + if pre_text.endswith(r'\---'): + pre_text = pre_text[:-4] + elif pre_text.endswith('---'): + pre_text = pre_text[:-3] + markdown_content = pre_text.strip() + + return markdown_content + + +def clean_body(file_path): + """Reads the file, cleans the text, and writes it back to the same file.""" + with open(file_path, 'r') as f: + content = f.read() + + markdown_content = process_text(content) + + # Write back to file. + with open(file_path, 'w') as f: + f.write(markdown_content) + + +if __name__ == '__main__': + if len(sys.argv) < 2: + script_name = os.path.basename(sys.argv[0]) + print(f"Usage: python3 {script_name} FILE") + sys.exit(1) + clean_body(sys.argv[1]) diff --git a/dev_tools/ci/dependabot_pr_description_cleaner_test.py b/dev_tools/ci/dependabot_pr_description_cleaner_test.py new file mode 100644 index 00000000..b645b46f --- /dev/null +++ b/dev_tools/ci/dependabot_pr_description_cleaner_test.py @@ -0,0 +1,342 @@ +# Copyright 2026 Google LLC +# +# 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 +# +# https://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. + +import dependabot_pr_description_cleaner as cleaner +import pytest + +# Sample 1 +SAMPLE_1 = r"""Bumps [raven-actions/actionlint](https://github.com/raven-actions/actionlint) from 2.0.1 to 2.1.0. +
+Release notes +

Sourced from raven-actions/actionlint's releases.

+
+

v2.1.0

+

🔄️ What's Changed

+ +

👥 Contributors

+

@​DariuszPorowski

+

See details of all code changes: https://github.com/raven-actions/actionlint/compare/v2.0.2...v2.1.0 since previous release.

+

v2.0.2

+

🔄️ What's Changed

+ +

👥 Contributors

+

@​DariuszPorowski, @​allejo, @​dependabot[bot], @​raven-actions-sync[bot], dependabot[bot] and raven-actions-sync[bot]

+

See details of all code changes: https://github.com/raven-actions/actionlint/compare/v2.0.1...v2.0.2 since previous release.

+
+
+
+Commits +
    +
  • 963d477 ci: update action versions in workflows and action metadata (#46)
  • +
  • 591b1df ci(deps): bump actions/checkout from 5 to 6 in the all group (#45)
  • +
  • fd1b743 fix: don't interfere with repo package.json (#43)
  • +
  • 14ed449 ci(deps): bump actions/cache from 4.2.4 to 4.3.0 in the all group (#42)
  • +
  • 0ecc789 ci(deps): bump actions/github-script from 7.0.1 to 8.0.0 in the all group (#41)
  • +
  • 193928d ci(deps): bump the all group across 1 directory with 2 updates (#40)
  • +
  • a9f1bde chore: synced file(s) with raven-actions/.workflows (#38)
  • +
  • e35ca82 chore: synced file(s) with raven-actions/.workflows (#37)
  • +
  • 8155254 chore: synced file(s) with raven-actions/.workflows (#36)
  • +
  • 0d9faa2 chore: synced file(s) with raven-actions/.workflows (#35)
  • +
  • Additional commits viewable in compare view
  • +
+
+
+ + +[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=raven-actions/actionlint&package-manager=github_actions&previous-version=2.0.1&new-version=2.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) + +Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. + +[//]: # (dependabot-automerge-start) +[//]: # (dependabot-automerge-end) + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it +- `@dependabot merge` will merge this PR after your CI passes on it +- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it +- `@dependabot cancel merge` will cancel a previously requested merge and block automerging +- `@dependabot reopen` will reopen this PR if it is closed +- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually +- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency +- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) + + +
+""" + +# Sample 2 +SAMPLE_2 = r"""Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.0 to 6.0.1. +
+Release notes +

Sourced from actions/checkout's releases.

+
+

v6.0.1

+

What's Changed

+ +

Full Changelog: https://github.com/actions/checkout/compare/v6...v6.0.1

+
+
+
+Commits + +
+
+ + +[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=6.0.0&new-version=6.0.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) + +Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. + +[//]: # (dependabot-automerge-start) +[//]: # (dependabot-automerge-end) + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it +- `@dependabot merge` will merge this PR after your CI passes on it +- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it +- `@dependabot cancel merge` will cancel a previously requested merge and block automerging +- `@dependabot reopen` will reopen this PR if it is closed +- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually +- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency +- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) + + +
+""" + +# Sample 3 +SAMPLE_3 = r"""Updates the requirements on [cmake](https://github.com/scikit-build/cmake-python-distributions) to permit the latest version. +
+Release notes +

Sourced from cmake's releases.

+
+

4.2.1

+ +

What's Changed

+ +

New Contributors

+ +

Full Changelog: https://github.com/scikit-build/cmake-python-distributions/compare/4.2.0...4.2.1

+
+
+
+Commits +
    +
  • 535af4c [Bot] Update to CMake 4.2.1 (#666)
  • +
  • b9233cd chore(deps): update clang to 21.1.8.0 (#670)
  • +
  • 5a1434b fix: workaround issue in lastversion with OpenSSL (#669)
  • +
  • dd81945 chore(deps): bump the actions group with 3 updates (#668)
  • +
  • 40acf75 chore(deps): update pre-commit hooks (#664)
  • +
  • a28030b fix: add missing f-string prefix for --parallel bootstrap arg (#665)
  • +
  • aa7b6bf chore(deps): bump the actions group with 3 updates (#663)
  • +
  • a611e25 chore(deps): update clang to 21.1.6.0 (#661)
  • +
  • c89cf39 chore: monthly updates (#660)
  • +
  • 597f158 chore: add changelog exclusion for bots (#658)
  • +
  • Additional commits viewable in compare view
  • +
+
+
+ + +Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. + +[//]: # (dependabot-automerge-start) +[//]: # (dependabot-automerge-end) + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it +- `@dependabot merge` will merge this PR after your CI passes on it +- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it +- `@dependabot cancel merge` will cancel a previously requested merge and block automerging +- `@dependabot reopen` will reopen this PR if it is closed +- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually +- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency +- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) + + +
+""" + + +def test_process_text_basic_conversion(): + html_input = "

Hello World

" + expected_output = "Hello **World**" + result = cleaner.process_text(html_input) + assert result.strip() == expected_output + + +def test_remove_commits_section(): + html_input = """ +

Some text

+
+ Commits +
  • Commit 1
+
+
+

More text

+ """ + # "Commits" section should be removed. + # html2text introduces newlines. + result = cleaner.process_text(html_input) + + assert "Commits" not in result + assert "Commit 1" not in result + assert "Some text" in result + assert "More text" in result + + +def test_remove_dependabot_commands(): + html_input = """ +

Content

+
+
+ Dependabot commands and options +

commands

+
+ """ + result = cleaner.process_text(html_input) + + assert "Content" in result + assert "Dependabot commands and options" not in result + + +def test_basic_example(): + # A simplified version of a real Dependabot body + html_input = """ + Bumps [package](link) from 1.0 to 2.0. +
+ Release notes +

Notes

+
+
+ Commits +
  • 123456
+
+
+ --- +
+ Dependabot commands and options +

help

+
+ """ + result = cleaner.process_text(html_input) + + assert "Bumps [package](link) from 1.0 to 2.0." in result + assert "Release notes" in result + assert "Commits" not in result + assert "123456" not in result + assert "Dependabot commands and options" not in result + + +@pytest.mark.parametrize("input_content", [SAMPLE_1, SAMPLE_2, SAMPLE_3]) +def test_clean_real_samples(input_content): + cleaned = cleaner.process_text(input_content) + + assert cleaned is not None + assert "Dependabot commands and options" not in cleaned + assert "
" not in cleaned + assert "
" not in cleaned + assert "Release notes" in cleaned + assert "Commits" not in cleaned + + +def test_idempotency(): + cleaned = cleaner.process_text(SAMPLE_1) + cleaned_again = cleaner.process_text(cleaned) + assert "Dependabot commands and options" not in cleaned_again + + # Check that links are preserved (and not escaped). + assert "[raven-actions/actionlint](https://github.com/raven-actions/actionlint)" in cleaned_again + assert "\\[raven-actions/actionlint\\]" not in cleaned_again + + +def test_markdown_preservation(): + # Ensure links are not escaped (basic check) + cleaned = cleaner.process_text(SAMPLE_1) + assert "[raven-actions/actionlint](https://github.com/raven-actions/actionlint)" in cleaned + + +def test_custom_separator_removal(): + content = "Some content\n\n--- \nDependabot commands and options\nJunk at the end" + cleaned = cleaner.process_text(content) + assert "Some content" in cleaned + assert "Dependabot commands and options" not in cleaned + assert "Junk at the end" not in cleaned + assert "---" not in cleaned + + +def test_no_separator_removal(): + content = "Some content\nDependabot commands and options\nJunk at the end" + cleaned = cleaner.process_text(content) + assert "Some content" in cleaned + assert "Dependabot commands and options" not in cleaned + assert "Junk at the end" not in cleaned From a97aa66b3a8c4a5ad4fbd6fb47955c1e97aa7fc2 Mon Sep 17 00:00:00 2001 From: mhucka Date: Fri, 2 Jan 2026 19:35:17 +0000 Subject: [PATCH 4/5] Fix formatting --- .../ci/dependabot_pr_description_cleaner.py | 20 +++++++++---------- .../dependabot_pr_description_cleaner_test.py | 10 ++++++++-- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/dev_tools/ci/dependabot_pr_description_cleaner.py b/dev_tools/ci/dependabot_pr_description_cleaner.py index ebb78f32..d366e9e1 100644 --- a/dev_tools/ci/dependabot_pr_description_cleaner.py +++ b/dev_tools/ci/dependabot_pr_description_cleaner.py @@ -33,10 +33,10 @@ def process_text(content): # Pattern: match the start of the details block for Commits, lazy match # until the closing details tag, and match the following br tag if present. content = re.sub( - r'
\s*Commits.*?
\s*()?', - '', + r"
\s*Commits.*?
\s*()?", + "", content, - flags=re.DOTALL | re.IGNORECASE + flags=re.DOTALL | re.IGNORECASE, ) h = html2text.HTML2Text() @@ -50,10 +50,10 @@ def process_text(content): pre_text = parts[0] pre_text = pre_text.rstrip() # Remove the marker if present at the end. - if pre_text.endswith(r'\---'): - pre_text = pre_text[:-4] - elif pre_text.endswith('---'): - pre_text = pre_text[:-3] + if pre_text.endswith(r"\---"): + pre_text = pre_text[:-4] + elif pre_text.endswith("---"): + pre_text = pre_text[:-3] markdown_content = pre_text.strip() return markdown_content @@ -61,17 +61,17 @@ def process_text(content): def clean_body(file_path): """Reads the file, cleans the text, and writes it back to the same file.""" - with open(file_path, 'r') as f: + with open(file_path, "r") as f: content = f.read() markdown_content = process_text(content) # Write back to file. - with open(file_path, 'w') as f: + with open(file_path, "w") as f: f.write(markdown_content) -if __name__ == '__main__': +if __name__ == "__main__": if len(sys.argv) < 2: script_name = os.path.basename(sys.argv[0]) print(f"Usage: python3 {script_name} FILE") diff --git a/dev_tools/ci/dependabot_pr_description_cleaner_test.py b/dev_tools/ci/dependabot_pr_description_cleaner_test.py index b645b46f..74dafd26 100644 --- a/dev_tools/ci/dependabot_pr_description_cleaner_test.py +++ b/dev_tools/ci/dependabot_pr_description_cleaner_test.py @@ -315,14 +315,20 @@ def test_idempotency(): assert "Dependabot commands and options" not in cleaned_again # Check that links are preserved (and not escaped). - assert "[raven-actions/actionlint](https://github.com/raven-actions/actionlint)" in cleaned_again + assert ( + "[raven-actions/actionlint](https://github.com/raven-actions/actionlint)" + in cleaned_again + ) assert "\\[raven-actions/actionlint\\]" not in cleaned_again def test_markdown_preservation(): # Ensure links are not escaped (basic check) cleaned = cleaner.process_text(SAMPLE_1) - assert "[raven-actions/actionlint](https://github.com/raven-actions/actionlint)" in cleaned + assert ( + "[raven-actions/actionlint](https://github.com/raven-actions/actionlint)" + in cleaned + ) def test_custom_separator_removal(): From 6e8574ee0a5e019389f55d945dc7b098a1ef8a78 Mon Sep 17 00:00:00 2001 From: mhucka Date: Fri, 2 Jan 2026 20:08:53 +0000 Subject: [PATCH 5/5] Disable pylint long-line warnings for this test file --- dev_tools/ci/dependabot_pr_description_cleaner_test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dev_tools/ci/dependabot_pr_description_cleaner_test.py b/dev_tools/ci/dependabot_pr_description_cleaner_test.py index 74dafd26..edc2fdcf 100644 --- a/dev_tools/ci/dependabot_pr_description_cleaner_test.py +++ b/dev_tools/ci/dependabot_pr_description_cleaner_test.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +# pylint: disable=line-too-long + import dependabot_pr_description_cleaner as cleaner import pytest