From 0da30213d42e8ec5067eb598cd0ebffc0cae74a1 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 31 Oct 2024 22:03:28 +0000 Subject: [PATCH 01/20] docker: Update tag This will need to be part of our release procedure going forward. Signed-off-by: Stephen Finucane --- tools/docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/docker/Dockerfile b/tools/docker/Dockerfile index 0a55b54db..1136d8e43 100644 --- a/tools/docker/Dockerfile +++ b/tools/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/getpatchwork/pyenv:latest +FROM ghcr.io/getpatchwork/pyenv:stable-3.2 ARG UID=1000 ARG GID=1000 From b35f6e3a792d096fc35c8c4168110426581685aa Mon Sep 17 00:00:00 2001 From: Thomas Monjalon Date: Fri, 16 Aug 2024 09:20:28 +0200 Subject: [PATCH 02/20] templates/submission: Fix alignment of commit message Preformatted content must not be indented because any space is kept in the output, making the content wrongly indented. When aligning message headers to the left, the new HTML code has been indented including some preformatted content indented with two spaces. The fix is to remove the indent of the content. Signed-off-by: Thomas Monjalon Fixes: fe34ab2ffad3 ("patch-detail: left align message headers") Reviewed-by: Robin Jarry Reviewed-by: Stephen Finucane (cherry picked from commit 13bbc0d94ea035c149f8acd042d2998778b9efd9) --- patchwork/templates/patchwork/submission.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patchwork/templates/patchwork/submission.html b/patchwork/templates/patchwork/submission.html index 85e7be4b4..e924934f6 100644 --- a/patchwork/templates/patchwork/submission.html +++ b/patchwork/templates/patchwork/submission.html @@ -262,7 +262,7 @@

Message

{{ submission.date }} UTC
-  {{ submission|commentsyntax }}
+{{ submission|commentsyntax }}
   
From 1455a92363500c0128eacc75785eb4aea5fd0015 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 31 Oct 2024 22:14:38 +0000 Subject: [PATCH 03/20] Release 3.2.1 Signed-off-by: Stephen Finucane --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 944880fa1..e4604e3af 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.2.0 +3.2.1 From b2374a0620f32ecd2e7c175a9c347c674ae03cc6 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 31 Oct 2024 22:15:18 +0000 Subject: [PATCH 04/20] Post-release version bump Signed-off-by: Stephen Finucane --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index e4604e3af..c6779f4bf 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.2.1 +3.2.2.alpha.0 From 3e924483395c260aae8ece1873a31f7266ce0ef2 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 31 Oct 2024 22:58:34 +0000 Subject: [PATCH 05/20] Fix version.txt Fixes: b2374a06 ("Post-release version bump") Signed-off-by: Stephen Finucane --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index c6779f4bf..4f8403122 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.2.2.alpha.0 +3.2.2.a.0 From 95682fd3de5b46cc05e1d5de2377571278dd62ab Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 31 Oct 2024 22:18:20 +0000 Subject: [PATCH 06/20] CI: Cancel builds upon a new push Signed-off-by: Stephen Finucane (cherry picked from commit 09939ba107bde85e0b9df35c0dbbf90428789a94) --- .github/workflows/ci.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5fb784df3..98a9583ae 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -3,6 +3,9 @@ name: CI on: - push - pull_request +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: lint: name: Run linters From 5c4b660ecefcbc349b6279ed7b987ae7ed3d5fc5 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 31 Oct 2024 22:59:18 +0000 Subject: [PATCH 07/20] tests: Dump more info if version test fails Signed-off-by: Stephen Finucane (cherry picked from commit fa14cf8480d9e515ed7a769adbd9eab33c6d82d9) --- patchwork/tests/test_version.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/patchwork/tests/test_version.py b/patchwork/tests/test_version.py index 8e512084c..9cbb13051 100644 --- a/patchwork/tests/test_version.py +++ b/patchwork/tests/test_version.py @@ -32,4 +32,9 @@ def test_validate_version(self): # if the tag is missing from one, it should be missing from the other # (and vice versa) - self.assertEqual(bool(str_match.group(1)), bool(git_match.group(1))) + self.assertEqual( + bool(str_match.group(1)), + bool(git_match.group(1)), + f'mismatch between git and version.txt post-release metadata: ' + f'git={git_match.group(1)!r}, version.txt={str_match.group(1)!r}', + ) From 794bdb9863dcb802f1d384da8ecc46f8783c219d Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Wed, 5 Oct 2022 16:24:05 +0200 Subject: [PATCH 08/20] css: make diff colors more accessible The colors used to display patch diffs are confusing. The context color is very similar to the added line color and the contrast between added and removed lines is very low. Originally, the choice of purple/blue (instead of the more common red/green palette) may have been made with colorblindness accessibility in mind. However, after inspecting the current colors with colorblindness "simulators", I found that the low contrast was consistent no matter what vision deficiency (if any) you might have. Update the colors to use a more common red/green palette. Add background colors to increase contrast for colorblind people. Use less confusing colors for context and diff hunks. Use normal line height to prevent background colors from overlapping. Use a different color for email quotes (blue) to avoid confusion with added lines. I have made a compilation of the current and updated color palette previews for normal vision and all common color deficiencies. I also included the same diff as seen from Github interface for reference. Link: http://files.diabeteman.com/patchwork-diff-colors/ Signed-off-by: Robin Jarry (cherry picked from commit c443cd9c0e96ca21842a188f9101b1a8244535ee) --- htdocs/css/style.css | 16 ++++++++-------- ...-colors-more-accessible-82eda58a89984d46.yaml | 5 +++++ 2 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/make-diff-colors-more-accessible-82eda58a89984d46.yaml diff --git a/htdocs/css/style.css b/htdocs/css/style.css index 9156aa6ee..1a7395109 100644 --- a/htdocs/css/style.css +++ b/htdocs/css/style.css @@ -17,7 +17,7 @@ h2 a, h2 span { } pre { - line-height: 110%; + line-height: normal; background-color: white; border-radius: 0; } @@ -354,15 +354,15 @@ button[class^=comment-action] { } .quote { - color: #007f00; + color: #365cb5; } -span.p_header { color: #2e8b57; font-weight: bold; } -span.p_chunk { color: #a52a2a; font-weight: bold; } -span.p_context { color: #a020f0; } -span.p_add { color: #008b8b; } -span.p_del { color: #6a5acd; } -span.p_mod { color: #0000ff; } +span.p_header { font-weight: bold; } +span.p_chunk { color: #329fb0; font-weight: bold; } +span.p_context { } +span.p_add { color: #1b9d09; background-color: #edffed; } +span.p_del { color: #c80101; background-color: #ffe2e2; } +span.p_mod { color: #a020f0; } .acked-by { color: #2d4566; diff --git a/releasenotes/notes/make-diff-colors-more-accessible-82eda58a89984d46.yaml b/releasenotes/notes/make-diff-colors-more-accessible-82eda58a89984d46.yaml new file mode 100644 index 000000000..f65995e51 --- /dev/null +++ b/releasenotes/notes/make-diff-colors-more-accessible-82eda58a89984d46.yaml @@ -0,0 +1,5 @@ +--- +other: + - | + The patch diff color palette was modified to make it more accessible for + all users, including those with common color deficiencies. From 5b5fef54af62a900fd971d0df1bbbf34d52fd4e7 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 1 Nov 2024 15:45:03 +0000 Subject: [PATCH 09/20] views: Switch logout to POST This was deprecated in 4.1 and removed in 5.0. I missed it. [1] https://docs.djangoproject.com/en/5.0/releases/4.1/#features-deprecated-in-4-1 Signed-off-by: Stephen Finucane (cherry picked from commit 45f47e96c6c90aefac37b02e0e15052f6ba41a90) --- htdocs/css/style.css | 28 ++++++++++++++++++++++++++++ templates/base.html | 7 ++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/htdocs/css/style.css b/htdocs/css/style.css index 1a7395109..c4025c050 100644 --- a/htdocs/css/style.css +++ b/htdocs/css/style.css @@ -56,6 +56,34 @@ pre { color: #999; } +ul.dropdown-menu > li > form { + display: block; + width: 100%; +} + +ul.dropdown-menu > li > form > button { + /* taken from bootstrap's styling for '.dropdown-menu > li > a' */ + background: none; + border: none; + cursor: pointer; + display: block; + padding: 3px 20px; + clear: both; + font-weight: 400; + line-height: 1.42857143; + color: #333; + white-space: nowrap; + width: 100%; + text-align: left; +} + +ul.dropdown-menu > li > form > button:hover { + /* taken from bootstrap's styling for '.dropdown-menu > li > a:hover' */ + color: #262626; + text-decoration: none; + background-color: #f5f5f5; +} + form { padding: 0em; margin: 0em; diff --git a/templates/base.html b/templates/base.html index 747da5922..9519ecc55 100644 --- a/templates/base.html +++ b/templates/base.html @@ -93,7 +93,12 @@
  • Administration
  • {% endif %}
  • View profile
  • -
  • Logout
  • +
  • +
    + {% csrf_token %} + +
    +
  • {% else %} From 723ed510a61b191434e8f538716ee7ac82ff53a3 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Sat, 6 Jun 2026 11:25:43 +0100 Subject: [PATCH 10/20] [Stable-Only] Bump GitHub Actions versions The versions we were using here are out of date and no longer supported. Bump them all. Signed-off-by: Stephen Finucane --- .github/workflows/ci.yaml | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 98a9583ae..69989a8f1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,15 +12,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v6 with: python-version: "3.13" + cache: 'pip' - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v6 with: - go-version: "1.21" + go-version: "1.24" - name: Install dependencies run: python -m pip install tox - name: Run tox @@ -43,7 +44,7 @@ jobs: MYSQL_ROOT_PASSWORD: root-${{ github.run_id }} services: postgres: - image: postgres:latest + image: postgres:17 env: POSTGRES_DB: ${{ env.DATABASE_NAME }} POSTGRES_PASSWORD: ${{ env.DATABASE_PASSWORD }} @@ -56,7 +57,7 @@ jobs: --health-timeout 5s --health-retries 5 mysql: - image: mysql:latest + image: mysql:8.4 env: MYSQL_DATABASE: ${{ env.DATABASE_NAME }} MYSQL_USER: ${{ env.DATABASE_USER }} @@ -71,9 +72,9 @@ jobs: --health-retries 5 steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python }} - name: Install Python dependencies @@ -102,19 +103,22 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v6 with: fetch-depth: 0 + - name: Fetch all branches + run: git fetch --all - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v6 with: python-version: "3.13" + cache: 'pip' - name: Install dependencies run: python -m pip install tox - name: Build docs (via tox) run: tox -e docs - name: Archive build results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v7 with: name: html-docs-build path: docs/_build/html @@ -129,13 +133,14 @@ jobs: COMPOSE_FILE: ${{ matrix.db == 'mysql' && 'docker-compose.yml' || (matrix.db == 'postgres' && 'docker-compose-pg.yml') || 'docker-compose-sqlite3.yml' }} steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v6 with: python-version: "3.13" + cache: 'pip' - name: Build docker-compose service run: | docker compose build --build-arg UID=$(id -u) --build-arg GID=$(id -g) From a4207f03e219f8b6471f84feb5cb5b1c571d6b5f Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Sun, 7 Jun 2026 17:01:19 +0100 Subject: [PATCH 11/20] [Stable-Only] docker: Pin postgres, mysql image versions Signed-off-by: Stephen Finucane --- docker-compose-pg.yml | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose-pg.yml b/docker-compose-pg.yml index 44bc3ec0a..3e1798783 100644 --- a/docker-compose-pg.yml +++ b/docker-compose-pg.yml @@ -1,7 +1,7 @@ --- services: db: - image: postgres:latest + image: postgres:17 volumes: - ./tools/docker/db/postdata:/var/lib/postgresql/data environment: diff --git a/docker-compose.yml b/docker-compose.yml index 73f080a49..936818bf6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ --- services: db: - image: mysql:latest + image: mysql:8.4 volumes: - ./tools/docker/db/data:/var/lib/mysql environment: From b01e4b3c265855cb17805e2d8b82641a826333a9 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Sat, 6 Jun 2026 12:07:35 +0100 Subject: [PATCH 12/20] CI: Avoid duplication of jobs Only run on pushes to main and stable branches. Signed-off-by: Stephen Finucane (cherry picked from commit 4c7d6d83bd295ba74c23316bfc2271092d946479) --- .github/workflows/ci.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 69989a8f1..fb13c86c8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,8 +1,11 @@ --- name: CI on: - - push - - pull_request + push: + branches: + - main + - stable/* + pull_request: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true From e5aef739f95c981e548b1f8f65e142fe18a44d8a Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Sun, 7 Jun 2026 16:46:11 +0100 Subject: [PATCH 13/20] CI: Reduce number of tests run for docker-compose workflow We do not need to run the entire test suite again. Speed things up. NOTE(stephenfin): Modified to use the correct lower and upper bound for this branch. Signed-off-by: Stephen Finucane (cherry picked from commit cd6a64e63c24c713488727fd4ea555c6070c1f1a) --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fb13c86c8..7ea0fff20 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -166,7 +166,7 @@ jobs: docker compose exec -T -- db \ sh -c "exec mysql -uroot -p\"\${MYSQL_ROOT_PASSWORD}\" -e \"GRANT ALL ON \\\`test\\_\${MYSQL_DATABASE}%\\\`.* to '\${MYSQL_USER}'@'%'; FLUSH PRIVILEGES;\"" - name: Run unittest - run: docker compose run -T --rm web tox + run: docker compose run -T --rm web tox -e pep8,docs,py39-django42,py313-django51 - name: Test normal startup run: | docker compose up --detach From 7074e4b75fcbaa08c5a203a45c9a56a5ecb7a890 Mon Sep 17 00:00:00 2001 From: Tales da Aparecida Date: Mon, 21 Oct 2024 09:14:03 -0300 Subject: [PATCH 14/20] models: optimize with_tag_counts using case-when Leverage PatchTag index replacing the subquery counter with a JOIN. The current code is too slow on MySQL, as it doesn't use indexes. The new approach is more direct and allows proper index usage. Signed-off-by: Tales da Aparecida (cherry picked from commit 52877f86048b0a9a8f99320ffbe871eae07f95eb) --- patchwork/models.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/patchwork/models.py b/patchwork/models.py index 4e5afc4ba..89ac23bc0 100644 --- a/patchwork/models.py +++ b/patchwork/models.py @@ -5,7 +5,6 @@ # SPDX-License-Identifier: GPL-2.0-or-later from collections import Counter -from collections import OrderedDict import datetime import random import re @@ -308,8 +307,6 @@ def with_tag_counts(self, project=None): # Project, and share the project.tags cache between all patch.project # references. qs = self.prefetch_related('project') - select = OrderedDict() - select_params = [] # All projects have the same tags, so we're good to go here if project: @@ -317,16 +314,24 @@ def with_tag_counts(self, project=None): else: tags = Tag.objects.all() + # Annotate the count of each Tag in a column for tag in tags: - select[tag.attr_name] = ( - 'coalesce(' - '(SELECT count FROM patchwork_patchtag' - ' WHERE patchwork_patchtag.patch_id=patchwork_patch.id' - ' AND patchwork_patchtag.tag_id=%s), 0)' + qs = qs.annotate( + **{ + tag.attr_name: models.Sum( + models.Case( + models.When( + patchtag__tag_id=tag.id, + then=models.F('patchtag__count'), + ), + default=models.Value(0), + output_field=models.IntegerField(), + ) + ) + } ) - select_params.append(tag.id) - return qs.extra(select=select, select_params=select_params) + return qs class PatchManager(models.Manager): From a1540caca8590f3735a2fb80342653a347940c9a Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Tue, 30 Jan 2024 08:22:09 +0000 Subject: [PATCH 15/20] notifications.py: don't crash if notifications recipient is invalid On Django 3:3.2.19, if recipient is not filled or is invalid, cron job fails with: Traceback (most recent call last): File "/usr/local/patchwork/./manage.py", line 17, in execute_from_command_line(sys.argv) File "/usr/lib/python3/dist-packages/django/core/management/__init__.py", line 419, in execute_from_command_line utility.execute() File "/usr/lib/python3/dist-packages/django/core/management/__init__.py", line 413, in execute self.fetch_command(subcommand).run_from_argv(self.argv) File "/usr/lib/python3/dist-packages/django/core/management/base.py", line 354, in run_from_argv self.execute(*args, **cmd_options) File "/usr/lib/python3/dist-packages/django/core/management/base.py", line 398, in execute output = self.handle(*args, **options) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/patchwork/patchwork/management/commands/cron.py", line 19, in handle errors = send_notifications() ^^^^^^^^^^^^^^^^^^^^ File "/usr/local/patchwork/patchwork/notifications.py", line 84, in send_notifications message.send() File "/usr/lib/python3/dist-packages/django/core/mail/message.py", line 284, in send return self.get_connection(fail_silently).send_messages([self]) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3/dist-packages/django/core/mail/backends/smtp.py", line 109, in send_messages sent = self._send(message) ^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3/dist-packages/django/core/mail/backends/smtp.py", line 122, in _send recipients = [sanitize_address(addr, encoding) for addr in email_message.recipients()] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3/dist-packages/django/core/mail/backends/smtp.py", line 122, in recipients = [sanitize_address(addr, encoding) for addr in email_message.recipients()] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3/dist-packages/django/core/mail/message.py", line 99, in sanitize_address address_parts = nm + localpart + domain ~~~^~~~~~~~~~~ TypeError: can only concatenate str (not "NoneType") to str As there's no point trying to send a notification to an invalid e-mail, just drops it. Signed-off-by: Mauro Carvalho Chehab Co-authored-by: Stephen Finucane [stephenfin: Updated to fix linter issues] (cherry picked from commit 1139b9ae0e09e589073c506d4209dea865621eb1) --- patchwork/notifications.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/patchwork/notifications.py b/patchwork/notifications.py index 6f33f8992..4dcfb5a80 100644 --- a/patchwork/notifications.py +++ b/patchwork/notifications.py @@ -73,6 +73,20 @@ def delete_notifications(): 'patchwork/mails/patch-change-notification.txt', context ) + # Got one case where recipient.email == "<>". That causes Django to fail with: + # File "/usr/lib/python3/dist-packages/django/core/mail/message.py", line 99, in sanitize_address + # address_parts = nm + localpart + domain + # ~~~^~~~~~~~~~~ + # TypeError: can only concatenate str (not "NoneType") to str + if ( + not recipient.email + or recipient.email == '<>' + or recipient.email == '' + ): + errors.append((recipient, 'Invalid recipient')) + delete_notifications() + continue + message = EmailMessage( subject=subject, body=content, From 1c587ac693a1432e95494f07138951bd7c5abcab Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Mon, 14 Apr 2025 12:58:33 +0530 Subject: [PATCH 16/20] templatetags: Render links as XHTML links Convert links in the commit message and comments, to be rendered as clickable XHTML links for easy navigation. Currently only http, https, ftp and git are recognized. And only links that are not broken across lines are recognized. Signed-off-by: Abdun Nihaal Closes: #591 (cherry picked from commit 980b27097a11c330b78b730d2877c2ea34c6a9e6) --- patchwork/templatetags/syntax.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/patchwork/templatetags/syntax.py b/patchwork/templatetags/syntax.py index 64773e5ab..d5cf405ba 100644 --- a/patchwork/templatetags/syntax.py +++ b/patchwork/templatetags/syntax.py @@ -47,6 +47,10 @@ def _compile(value): _span = '%s' +_comment_link_re = re.compile(r'(https|http|git|ftp)://[^<)\s]+', re.I) + +_link = '%s' + @register.filter def patchsyntax(patch): @@ -74,5 +78,8 @@ def commentsyntax(submission): for r, cls in _comment_span_res: content = r.sub(lambda x: _span % (cls, x.group(0)), content) + content = _comment_link_re.sub( + lambda x: _link % (x.group(0), x.group(0)), content + ) return mark_safe(content) From b75766d97dc84fb7bdeeed2c31fb43818b161a6b Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Sun, 7 Jun 2026 18:17:50 +0100 Subject: [PATCH 17/20] readthedocs: Fix builds Signed-off-by: Stephen Finucane (cherry picked from commit 338a976125fa492eb113d871df621bfd71068201) --- .readthedocs.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index e19fe966e..355742bbd 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,14 +1,15 @@ --- version: 2 +sphinx: + builder: "dirhtml" + configuration: docs/conf.py python: install: - requirements: docs/requirements.txt -sphinx: - builder: "dirhtml" build: - os: "ubuntu-22.04" + os: "ubuntu-24.04" tools: - python: "3.11" + python: "3.14" jobs: post_checkout: - git fetch --unshallow From 6ab05c907d21d51c0ced4c81a91587840e3f7c8c Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Sun, 7 Jun 2026 18:45:09 +0100 Subject: [PATCH 18/20] README: Remove codecov badge Signed-off-by: Stephen Finucane (cherry picked from commit f51e30684ee10f093cc42df9783f68c009e75cba) --- README.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.rst b/README.rst index ebde1f363..002fb5ea1 100644 --- a/README.rst +++ b/README.rst @@ -6,10 +6,6 @@ Patchwork :target: https://pyup.io/repos/github/getpatchwork/patchwork/ :alt: Requirements Status -.. image:: https://codecov.io/gh/getpatchwork/patchwork/branch/main/graph/badge.svg - :target: https://codecov.io/gh/getpatchwork/patchwork - :alt: Codecov - .. image:: https://github.com/getpatchwork/patchwork/actions/workflows/ci.yaml/badge.svg :target: https://github.com/getpatchwork/patchwork/actions/workflows/ci.yaml :alt: Build Status From 481adeb0606f3867de71755c39d0ee10f1d264bb Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Mon, 8 Jun 2026 14:00:55 +0200 Subject: [PATCH 19/20] parsemail: fix SeriesReference race with concurrent delivery When multiple parsemail processes run in parallel (e.g. postfix delivering several messages from the same series at once), two processes can try to create a SeriesReference for the same msgid simultaneously. The second one fails with an IntegrityError: django.db.utils.IntegrityError: duplicate key value violates unique constraint "patchwork_seriesreference_project_id_msgid_..." DETAIL: Key (project_id, msgid)=(2, <...>) already exists. This can result in incomplete series that never reach the "received_all" state because the failed parsemail invocation prevents one of the patches from being recorded. The existing get/create pattern has a classic TOCTOU race: the get succeeds (no reference found), but by the time create runs, another process has already inserted the row. Replace both the try/get/ except/create block and the bare create call with get_or_create which handles the race atomically at the database level. Signed-off-by: Robin Jarry Reviwed-by: Stephen Finucane (cherry picked from commit 3558907533a1a420ccceec6935635e7c2245eb69) --- patchwork/parser.py | 30 +++++++++---------- .../parsemail-race-fix-e5f6g7h8i9j0k1l2.yaml | 5 ++++ 2 files changed, 19 insertions(+), 16 deletions(-) create mode 100644 releasenotes/notes/parsemail-race-fix-e5f6g7h8i9j0k1l2.yaml diff --git a/patchwork/parser.py b/patchwork/parser.py index 09a53a08e..b03db4363 100644 --- a/patchwork/parser.py +++ b/patchwork/parser.py @@ -1256,20 +1256,18 @@ def parse_mail(mail, list_id=None): # later one. for ref in refs + [msgid]: ref = ref[:255] - # we don't want duplicates - try: - # we could have a ref to a previous series. - # (For example, a series sent in reply to - # another series.) That should not create a - # series ref for this series, so check for the - # msg-id only, not the msg-id/series pair. - SeriesReference.objects.get( - msgid=ref, project=project - ) - except SeriesReference.DoesNotExist: - SeriesReference.objects.create( - msgid=ref, project=project, series=series - ) + # We could have a ref to a previous series. (For + # example, a series sent in reply to another + # series.) That should not create a series ref for + # this series, so check for the msg-id only, not + # the msg-id/series pair. Use get_or_create to + # avoid races when multiple parsemail processes run + # in parallel. + SeriesReference.objects.get_or_create( + msgid=ref, + project=project, + defaults={'series': series}, + ) # attempt to pull the series in again, raising an # exception if we lost the race when creating a series @@ -1353,8 +1351,8 @@ def parse_mail(mail, list_id=None): # we don't save the in-reply-to or references fields # for a cover letter, as they can't refer to the same # series - SeriesReference.objects.create( - msgid=msgid, project=project, series=series + SeriesReference.objects.get_or_create( + msgid=msgid, project=project, defaults={'series': series} ) with transaction.atomic(): diff --git a/releasenotes/notes/parsemail-race-fix-e5f6g7h8i9j0k1l2.yaml b/releasenotes/notes/parsemail-race-fix-e5f6g7h8i9j0k1l2.yaml new file mode 100644 index 000000000..f08698195 --- /dev/null +++ b/releasenotes/notes/parsemail-race-fix-e5f6g7h8i9j0k1l2.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fix a race condition in SeriesReference creation during concurrent + email delivery. From 0ea35f855101a367195e8407f0a085c5be861ed6 Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Mon, 8 Jun 2026 13:59:43 +0200 Subject: [PATCH 20/20] parsemail: wrap parse_mail() in a single transaction Wrap the entire parse_mail() call in transaction.atomic() so that all database writes from email parsing run inside a single transaction. The existing transaction.atomic() blocks inside parse_mail() become savepoints within this outer transaction. The series deduplication retry logic continues to work since savepoint rollbacks are scoped to their own savepoint. This also ensures that any on_commit() callbacks registered by signal handlers only fire after the full email has been parsed and all patch/series associations are committed. Signed-off-by: Robin Jarry Signed-off-by: Stephen Finucane Reviewed-by: Stephen Finucane [stephenfin: Slight tweaks to the release notes] (cherry picked from commit 644cef7cd369abd39eba9523f8ecbc16d2ea4206) --- patchwork/management/commands/parsemail.py | 4 +++- .../notes/parsemail-transaction-d4e5f6g7h8i9j0k1.yaml | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/parsemail-transaction-d4e5f6g7h8i9j0k1.yaml diff --git a/patchwork/management/commands/parsemail.py b/patchwork/management/commands/parsemail.py index bcb257fe9..2f90047a9 100644 --- a/patchwork/management/commands/parsemail.py +++ b/patchwork/management/commands/parsemail.py @@ -8,6 +8,7 @@ import sys from django.core.management import base +from django.db import transaction from patchwork.parser import parse_mail from patchwork.parser import DuplicateMailError @@ -57,7 +58,8 @@ def handle(self, *args, **options): # broken email (ValueError): 1 (this could be noisy, if it's an issue # we could use a different return code) try: - result = parse_mail(mail, options['list_id']) + with transaction.atomic(): + result = parse_mail(mail, options['list_id']) if result is None: logger.warning('Nothing added to database') except DuplicateMailError as exc: diff --git a/releasenotes/notes/parsemail-transaction-d4e5f6g7h8i9j0k1.yaml b/releasenotes/notes/parsemail-transaction-d4e5f6g7h8i9j0k1.yaml new file mode 100644 index 000000000..5a224ca23 --- /dev/null +++ b/releasenotes/notes/parsemail-transaction-d4e5f6g7h8i9j0k1.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Wrap the entire ``parse_mail()`` function in a single database + transaction to prevent partial state from being visible to + concurrent readers.