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
