The command re-parses subject headers of patches from given projects
and adds label information to the database. The main usecase is to
refresh label information on existing patches after adding a new label
to the project.

Signed-off-by: Franciszek Stachura <[email protected]>
---
 docs/deployment/management.rst           | 27 +++++++++
 patchwork/management/commands/relabel.py | 75 ++++++++++++++++++++++++
 2 files changed, 102 insertions(+)
 create mode 100644 patchwork/management/commands/relabel.py

diff --git a/docs/deployment/management.rst b/docs/deployment/management.rst
index dcee9ff5..dcbbf219 100644
--- a/docs/deployment/management.rst
+++ b/docs/deployment/management.rst
@@ -192,3 +192,30 @@ patches for these new tags.
 .. option:: patch_id
 
    a patch ID number. If not supplied, all patches will be updated.
+
+.. _command-relabel:
+
+relabel
+~~~~~~~
+
+.. program:: manage.py relabel
+
+Relabel patches based on the subject line.
+
+.. code-block:: shell
+
+   ./manage.py relabel [PROJECT [PROJECT...]]
+
+Patchwork extracts labels from square brackets at the beginning of the subject
+line, eg. ``[label1, label2] Patch title``.
+
+Only labels created in the admin interface will be recognized.
+The script prioritizes project-specific labels if both project-specific and
+general labels with the same name exist.
+
+The script will not remove labels manually added to patches, but will re-add
+labels manually removed from patches.
+
+.. option:: PROJECT
+
+   list of IDs of projects to update. Relabels all projects if none specified.
diff --git a/patchwork/management/commands/relabel.py 
b/patchwork/management/commands/relabel.py
new file mode 100644
index 00000000..954f2cd3
--- /dev/null
+++ b/patchwork/management/commands/relabel.py
@@ -0,0 +1,75 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2026, The Linux Foundation
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+from django.core.management.base import BaseCommand
+
+from email.parser import HeaderParser
+
+from patchwork.models import Label, Patch
+from patchwork.parser import clean_subject
+
+
+class Command(BaseCommand):
+    help = (
+        'Update labels for existing patches based on the subject line. '
+        'NOTE: Labels have to be created in the admin interface first.'
+    )
+
+    def add_arguments(self, parser):
+        parser.add_argument(
+            'projects',
+            metavar='PROJECT',
+            nargs='*',
+            help='list of listIDs of projects to be relabeld',
+        )
+
+    def handle(self, *args, **options):
+        query = Patch.objects.all().only('id', 'headers', 'project_id')
+        labels = {
+            (label.name, label.project_id): label
+            for label in Label.objects.all()
+        }
+        labels_to_add = []
+
+        if options['projects']:
+            query = query.filter(project__listid__in=options['projects'])
+
+        count = query.count()
+
+        for i, patch in enumerate(query.iterator(chunk_size=1000)):
+            parser = HeaderParser()
+            headers = parser.parsestr(patch.headers)
+            subject = headers['Subject']
+            if subject is None:
+                continue
+
+            _, prefixes = clean_subject(subject)
+
+            for prefix in set(prefixes):
+                label = labels.get((prefix, patch.project_id))
+
+                if label is None:
+                    label = labels.get((prefix, None))
+
+                if label is not None:
+                    labels_to_add.append(
+                        Patch.labels.through(
+                            patch_id=patch.id, label_id=label.id
+                        )
+                    )
+
+            if (i % 100) == 0:
+                self.stdout.write('%06d/%06d\r' % (i, count), ending='')
+                self.stdout.flush()
+
+            if len(labels_to_add) >= 1000:
+                Patch.labels.through.objects.bulk_create(labels_to_add)
+                labels_to_add = []
+
+        if len(labels_to_add) > 0:
+            Patch.labels.through.objects.bulk_create(labels_to_add)
+            labels_to_add = []
+
+        self.stdout.write('\ndone')
-- 
2.55.0

_______________________________________________
Patchwork mailing list
[email protected]
https://lists.ozlabs.org/listinfo/patchwork

Reply via email to