Skip to content

Commit 2fedf40

Browse files
committed
parser: Implement cover->patch label inheritance
Labels parsed out of a cover letter are automatically assigned to all patches in the series, even if the cover letter comes in late.
1 parent 63428b1 commit 2fedf40

4 files changed

Lines changed: 150 additions & 8 deletions

File tree

‎patchwork/management/commands/relabel.py‎

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from email.parser import HeaderParser
99

10-
from patchwork.models import Label, Patch
10+
from patchwork.models import Label, Patch, Cover, Series
1111
from patchwork.parser import clean_subject
1212

1313

@@ -17,7 +17,7 @@ def get_labels_per_project():
1717
global_labels = list(Label.objects.filter(project_id=None).all())
1818

1919
labels_per_project = {}
20-
for label in Label.objects.filter(project_id__isnull=True).all():
20+
for label in Label.objects.filter(project_id__isnull=False).all():
2121
labels_list = labels_per_project.get(label.project_id, [])
2222
labels_list.append(label)
2323
labels_per_project[label.project_id] = labels_list
@@ -116,9 +116,76 @@ def update_patches(self, projects):
116116

117117
self.flush_patches()
118118

119+
def flush_covers(self):
120+
if len(self.covers_to_update) > 0:
121+
Cover.objects.bulk_update(self.covers_to_update, ['name'])
122+
Series.objects.bulk_update(self.series_to_update, ['name'])
123+
self.covers_to_update = []
124+
self.series_to_update = []
125+
self.covers_to_update_ids = set()
126+
127+
if len(self.labels_to_add) > 0:
128+
Cover.labels.through.objects.bulk_create(self.labels_to_add)
129+
self.labels_to_add = []
130+
131+
def update_covers(self, projects):
132+
self.covers_to_update = []
133+
self.series_to_update = []
134+
self.covers_to_update_ids = set()
135+
self.labels_to_add = []
136+
137+
query = Cover.objects.all().only('id', 'headers', 'project_id')
138+
139+
if projects:
140+
query = query.filter(project__listid__in=projects)
141+
142+
count = query.count()
143+
144+
for i, cover in enumerate(query.iterator(chunk_size=1000)):
145+
project_labels = self.labels_per_project.get(
146+
cover.project_id, self.global_labels
147+
)
148+
149+
name, cleaned_prefixes = get_name_and_labels(
150+
cover.headers, [label.name for label in project_labels]
151+
)
152+
if name is None or cleaned_prefixes is None:
153+
continue
154+
155+
for label in project_labels:
156+
if (
157+
label.name in cleaned_prefixes
158+
and label not in cover.labels.all()
159+
):
160+
self.labels_to_add.append(
161+
Cover.labels.through(
162+
cover_id=cover.id, label_id=label.id
163+
)
164+
)
165+
if cover.id not in self.covers_to_update_ids:
166+
series = Series.objects.get(cover_letter=cover)
167+
cover.name = name[:255]
168+
series.name = name[:255]
169+
self.covers_to_update.append(cover)
170+
self.series_to_update.append(series)
171+
self.covers_to_update_ids.add(cover.id)
172+
173+
if (i % 100) == 0:
174+
self.stdout.write('%06d/%06d\r' % (i, count), ending='')
175+
self.stdout.flush()
176+
177+
if len(self.covers_to_update) >= 1000:
178+
self.flush_covers()
179+
180+
self.flush_covers()
181+
119182
def handle(self, *args, **options):
120183
self.labels_per_project, self.global_labels = get_labels_per_project()
121184

185+
self.stdout.write('\nupdating covers')
186+
187+
self.update_covers(options['projects'])
188+
122189
self.stdout.write('\nupdating patches')
123190

124191
self.update_patches(options['projects'])

‎patchwork/models.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,12 @@ def add_cover_letter(self, cover):
11131113

11141114
self.save()
11151115

1116+
labels = [label.id for label in cover.labels.all()]
1117+
if len(labels) != 0:
1118+
for patch in Patch.objects.filter(series=self).all():
1119+
patch.labels.add(*labels)
1120+
patch.save()
1121+
11161122
def add_patch(self, patch, number):
11171123
"""Add a patch to the series."""
11181124
# both user defined names and cover letter-based names take precedence
@@ -1122,6 +1128,9 @@ def add_patch(self, patch, number):
11221128

11231129
patch.series = self
11241130
patch.number = number
1131+
if self.cover_letter:
1132+
labels = [label.id for label in self.cover_letter.labels.all()]
1133+
patch.labels.add(*labels)
11251134
patch.save()
11261135

11271136
return patch

‎patchwork/parser.py‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1477,16 +1477,18 @@ def parse_mail(mail, list_id=None):
14771477
submitter=author,
14781478
content=message,
14791479
)
1480+
if labels:
1481+
cover_letter.labels.set(labels)
14801482

1481-
logger.debug('Cover letter saved')
1483+
series.add_cover_letter(cover_letter)
14821484

1483-
series.add_cover_letter(cover_letter)
1485+
logger.debug('Cover letter saved')
14841486

1485-
# cover letters are permitted to specify dependencies for the
1486-
# entire patch series; parse them
1487-
series.add_dependencies(parse_depends_on(message))
1487+
# cover letters are permitted to specify dependencies for the
1488+
# entire patch series; parse them
1489+
series.add_dependencies(parse_depends_on(message))
14881490

1489-
return cover_letter
1491+
return cover_letter
14901492

14911493
# comments
14921494

‎patchwork/tests/unit/test_parser.py‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,3 +1659,67 @@ def test_local_global_labels(self):
16591659
patch = self.create_email('[PATCH,RFC] test subject', self.project2)
16601660
self.cmp_labels(patch, [self.label_rfc_global])
16611661
self.assertEqual(patch.name, 'test subject')
1662+
1663+
def test_cover_patch_inheritance(self):
1664+
cover_msgid = make_msgid()
1665+
cover_email = create_email(
1666+
'test cover letter',
1667+
msgid=cover_msgid,
1668+
subject='[RFC,label2,0/2] a cover letter',
1669+
listid=self.project.listid,
1670+
)
1671+
_parse_mail(cover_email)
1672+
cover = Cover.objects.filter(msgid=cover_msgid)[0]
1673+
self.cmp_labels(cover, [self.label_rfc, self.label2])
1674+
1675+
patch1_msgid = make_msgid()
1676+
patch = self.create_email(
1677+
'[label,PATCH,1/2] test patch',
1678+
self.project,
1679+
in_reply_to=cover_msgid,
1680+
msgid=patch1_msgid,
1681+
)
1682+
self.cmp_labels(patch, [self.label, self.label_rfc, self.label2])
1683+
1684+
patch2 = self.create_email(
1685+
'[PATCH,2/2] test patch',
1686+
self.project,
1687+
in_reply_to=patch1_msgid,
1688+
)
1689+
self.cmp_labels(patch2, [self.label_rfc, self.label2])
1690+
1691+
def test_cover_patch_inheritance_out_of_order(self):
1692+
cover_msgid = make_msgid()
1693+
1694+
patch1_msgid = make_msgid()
1695+
patch1 = self.create_email(
1696+
'[label,PATCH,1/2] test patch',
1697+
self.project,
1698+
in_reply_to=cover_msgid,
1699+
msgid=patch1_msgid,
1700+
)
1701+
self.cmp_labels(patch1, [self.label])
1702+
1703+
patch2 = self.create_email(
1704+
'[PATCH,2/2] test patch',
1705+
self.project,
1706+
in_reply_to=patch1_msgid,
1707+
)
1708+
self.cmp_labels(patch2, [])
1709+
1710+
cover_email = create_email(
1711+
'test cover letter',
1712+
msgid=cover_msgid,
1713+
subject='[RFC,0/2] a cover letter',
1714+
listid=self.project.listid,
1715+
)
1716+
_parse_mail(cover_email)
1717+
cover = Cover.objects.get(msgid=cover_msgid)
1718+
1719+
self.cmp_labels(cover, [self.label_rfc])
1720+
1721+
patch1 = Patch.objects.get(msgid=patch1_msgid)
1722+
self.cmp_labels(patch1, [self.label, self.label_rfc])
1723+
1724+
patch2 = Patch.objects.get(msgid=patch2.msgid)
1725+
self.cmp_labels(patch2, [self.label_rfc])

0 commit comments

Comments
 (0)