Skip to content

Commit bfa9394

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 15ab466 commit bfa9394

2 files changed

Lines changed: 122 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: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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_to_add = []
31+
32+
labels_per_project = {}
33+
for label in Label.objects.all():
34+
tmp = labels_per_project.get(label.project_id, [])
35+
tmp.append(label.name)
36+
labels_per_project[label.project_id] = tmp
37+
38+
patches_to_update = []
39+
patches_to_update_ids = set()
40+
41+
if options['projects']:
42+
query = query.filter(project__listid__in=options['projects'])
43+
44+
count = query.count()
45+
46+
for i, patch in enumerate(query.iterator(chunk_size=1000)):
47+
parser = HeaderParser()
48+
headers = parser.parsestr(patch.headers)
49+
subject = headers['Subject']
50+
prefixes = []
51+
if subject is None:
52+
continue
53+
54+
project_labels = labels_per_project.get(
55+
patch.project.id, []
56+
) + labels_per_project.get(None, [])
57+
58+
name, prefixes, cleaned_prefixes = clean_subject(
59+
subject, project_labels
60+
)
61+
62+
for label in project_labels:
63+
if label in cleaned_prefixes and label not in patch.labels:
64+
labels_to_add.append(
65+
Patch.labels.through(
66+
patch_id=patch.id, label_id=label.id
67+
)
68+
)
69+
patch.labels.append(label)
70+
if patch.id not in patches_to_update_ids:
71+
patch.name = name[:255]
72+
patches_to_update.append(patch)
73+
patches_to_update_ids.add(patch.id)
74+
75+
if (i % 100) == 0:
76+
self.stdout.write('%06d/%06d\r' % (i, count), ending='')
77+
self.stdout.flush()
78+
79+
if len(patches_to_update) >= 1000:
80+
Patch.objects.bulk_update(patches_to_update, ['name'])
81+
patches_to_update = []
82+
patches_to_update_ids = set()
83+
if len(labels_to_add) >= 1000:
84+
Patch.labels.through.objects.bulk_create(labels_to_add)
85+
labels_to_add = []
86+
87+
if len(patches_to_update) > 0:
88+
Patch.objects.bulk_update(patches_to_update, ['name'])
89+
patches_to_update = []
90+
patches_to_update_ids = set()
91+
if len(labels_to_add) > 0:
92+
Patch.labels.through.objects.bulk_create(labels_to_add)
93+
labels_to_add = []
94+
95+
self.stdout.write('\ndone')

0 commit comments

Comments
 (0)