Skip to content

Commit 1fe51ae

Browse files
committed
management: Add relabel command
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.
1 parent c8e147e commit 1fe51ae

2 files changed

Lines changed: 102 additions & 0 deletions

File tree

docs/deployment/management.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,30 @@ patches for these new tags.
192192
.. option:: patch_id
193193
194194
a patch ID number. If not supplied, all patches will be updated.
195+
196+
.. _command-relabel:
197+
198+
relabel
199+
~~~~~~~
200+
201+
.. program:: manage.py relabel
202+
203+
Relabel patches based on the subject line.
204+
205+
.. code-block:: shell
206+
207+
./manage.py relabel [PROJECT [PROJECT...]]
208+
209+
Patchwork extracts labels from square brackets at the beginning of the subject
210+
line, eg. ``[label1, label2] Patch title``.
211+
212+
Only labels created in the admin interface will be recognized.
213+
The script prioritizes project-specific labels if both project-specific and
214+
general labels with the same name exist.
215+
216+
The script will not remove labels manually added to patches, but will re-add
217+
labels manually removed from patches.
218+
219+
.. option:: PROJECT
220+
221+
list of IDs of projects to update. Relabels all projects if none specified.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Patchwork - automated patch tracking system
2+
# Copyright (C) 2026, The Linux Foundation
3+
#
4+
# SPDX-License-Identifier: GPL-2.0-or-later
5+
6+
from django.core.management.base import BaseCommand
7+
8+
from email.parser import HeaderParser
9+
10+
from patchwork.models import Label, Patch
11+
from patchwork.parser import clean_subject
12+
13+
14+
class Command(BaseCommand):
15+
help = (
16+
'Update labels for existing patches based on the subject line. '
17+
'NOTE: Labels have to be created in the admin interface first.'
18+
)
19+
20+
def add_arguments(self, parser):
21+
parser.add_argument(
22+
'projects',
23+
metavar='PROJECT',
24+
nargs='*',
25+
help='list of listIDs of projects to be relabeld',
26+
)
27+
28+
def handle(self, *args, **options):
29+
query = Patch.objects.all().only('id', 'headers', 'project_id')
30+
labels = {
31+
(label.name, label.project_id): label
32+
for label in Label.objects.all()
33+
}
34+
labels_to_add = []
35+
36+
if options['projects']:
37+
query = query.filter(project__listid__in=options['projects'])
38+
39+
count = query.count()
40+
41+
for i, patch in enumerate(query.iterator(chunk_size=1000)):
42+
parser = HeaderParser()
43+
headers = parser.parsestr(patch.headers)
44+
subject = headers['Subject']
45+
if subject is None:
46+
continue
47+
48+
_, prefixes = clean_subject(subject)
49+
50+
for prefix in set(prefixes):
51+
label = labels.get((prefix, patch.project_id))
52+
53+
if label is None:
54+
label = labels.get((prefix, None))
55+
56+
if label is not None:
57+
labels_to_add.append(
58+
Patch.labels.through(
59+
patch_id=patch.id, label_id=label.id
60+
)
61+
)
62+
63+
if (i % 100) == 0:
64+
self.stdout.write('%06d/%06d\r' % (i, count), ending='')
65+
self.stdout.flush()
66+
67+
if len(labels_to_add) >= 1000:
68+
Patch.labels.through.objects.bulk_create(labels_to_add)
69+
labels_to_add = []
70+
71+
if len(labels_to_add) > 0:
72+
Patch.labels.through.objects.bulk_create(labels_to_add)
73+
labels_to_add = []
74+
75+
self.stdout.write('\ndone')

0 commit comments

Comments
 (0)