Skip to content

Commit cdf43a7

Browse files
committed
migrations: Don't attempt to rehome patches
Migration 0039 attempts to move patches that have ended up in an arbitrary series due to race conditions into the correct series. However, there are a number of race conditions that can occur here that make this particularly tricky to do. Given that series are really just arbitary metadata, it's not really necessary to do this...so don't. Instead, just delete the series references that identical message IDs and below to the same project, allowing us to add the uniqueness constraint and prevent the issue bubbling up in the future. This means we're still left with orphaned series but these could be fixed manually, if necessary. Signed-off-by: Stephen Finucane <stephen@that.guru> Tested-by: Ali Alnubani <alialnu@mellanox.com> Closes: #340
1 parent d6ee204 commit cdf43a7

File tree

1 file changed

+46
-64
lines changed

1 file changed

+46
-64
lines changed

patchwork/migrations/0039_unique_series_references.py

Lines changed: 46 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -3,62 +3,6 @@
33
import django.db.models.deletion
44

55

6-
def merge_duplicate_series(apps, schema_editor):
7-
SeriesReference = apps.get_model('patchwork', 'SeriesReference')
8-
Patch = apps.get_model('patchwork', 'Patch')
9-
10-
msgid_seriesrefs = {}
11-
12-
# find all SeriesReference that share a msgid but point to different series
13-
# and decide which of the series is going to be the authoritative one
14-
msgid_counts = (
15-
SeriesReference.objects.values('msgid')
16-
.annotate(count=Count('msgid'))
17-
.filter(count__gt=1)
18-
)
19-
for msgid_count in msgid_counts:
20-
msgid = msgid_count['msgid']
21-
chosen_ref = None
22-
for series_ref in SeriesReference.objects.filter(msgid=msgid):
23-
if series_ref.series.cover_letter:
24-
if chosen_ref:
25-
# I don't think this can happen, but explode if it does
26-
raise Exception(
27-
"Looks like you've got two or more series that share "
28-
"some patches but do not share a cover letter. Unable "
29-
"to auto-resolve."
30-
)
31-
32-
# if a series has a cover letter, that's the one we'll group
33-
# everything under
34-
chosen_ref = series_ref
35-
36-
if not chosen_ref:
37-
# if none of the series have cover letters, simply use the last
38-
# one (hint: this relies on Python's weird scoping for for loops
39-
# where 'series_ref' is still accessible outside the loop)
40-
chosen_ref = series_ref
41-
42-
msgid_seriesrefs[msgid] = chosen_ref
43-
44-
# reassign any patches referring to non-authoritative series to point to
45-
# the authoritative one, and delete the other series; we do this separately
46-
# to allow us a chance to raise the exception above if necessary
47-
for msgid, chosen_ref in msgid_seriesrefs.items():
48-
for series_ref in SeriesReference.objects.filter(msgid=msgid):
49-
if series_ref == chosen_ref:
50-
continue
51-
52-
# update the patches to point to our chosen series instead, on the
53-
# assumption that all other metadata is correct
54-
for patch in Patch.objects.filter(series=series_ref.series):
55-
patch.series = chosen_ref.series
56-
patch.save()
57-
58-
# delete the other series (which will delete the series ref)
59-
series_ref.series.delete()
60-
61-
626
def copy_project_field(apps, schema_editor):
637
if connection.vendor == 'postgresql':
648
schema_editor.execute(
@@ -67,15 +11,15 @@ def copy_project_field(apps, schema_editor):
6711
SET project_id = patchwork_series.project_id
6812
FROM patchwork_series
6913
WHERE patchwork_seriesreference.series_id = patchwork_series.id
70-
"""
14+
"""
7115
)
7216
elif connection.vendor == 'mysql':
7317
schema_editor.execute(
7418
"""
7519
UPDATE patchwork_seriesreference, patchwork_series
7620
SET patchwork_seriesreference.project_id = patchwork_series.project_id
7721
WHERE patchwork_seriesreference.series_id = patchwork_series.id
78-
""" # noqa
22+
""" # noqa
7923
)
8024
else:
8125
SeriesReference = apps.get_model('patchwork', 'SeriesReference')
@@ -87,14 +31,49 @@ def copy_project_field(apps, schema_editor):
8731
series_ref.save()
8832

8933

34+
def delete_duplicate_series(apps, schema_editor):
35+
if connection.vendor == 'postgresql':
36+
schema_editor.execute(
37+
"""
38+
DELETE
39+
FROM
40+
patchwork_seriesreference a
41+
USING patchwork_seriesreference b
42+
WHERE
43+
a.id < b.id
44+
AND a.project_id = b.project_id
45+
AND a.msgid = b.msgid
46+
"""
47+
)
48+
elif connection.vendor == 'mysql':
49+
schema_editor.execute(
50+
"""
51+
DELETE a FROM patchwork_seriesreference a
52+
INNER JOIN patchwork_seriesreference b
53+
WHERE
54+
a.id < b.id
55+
AND a.project_id = b.project_id
56+
AND a.msgid = b.msgid
57+
"""
58+
)
59+
else:
60+
Project = apps.get_model('patchwork', 'Project')
61+
SeriesReference = apps.get_model('patchwork', 'SeriesReference')
62+
63+
for project in Project.objects.all():
64+
(
65+
SeriesReference.objects.filter(project=project)
66+
.annotate(count=Count('msgid'))
67+
.filter(count__gt=1)
68+
.delete()
69+
)
70+
71+
9072
class Migration(migrations.Migration):
9173

9274
dependencies = [('patchwork', '0038_state_slug')]
9375

9476
operations = [
95-
migrations.RunPython(
96-
merge_duplicate_series, migrations.RunPython.noop, atomic=False
97-
),
9877
migrations.AddField(
9978
model_name='seriesreference',
10079
name='project',
@@ -104,12 +83,12 @@ class Migration(migrations.Migration):
10483
to='patchwork.Project',
10584
),
10685
),
107-
migrations.AlterUniqueTogether(
108-
name='seriesreference', unique_together={('project', 'msgid')}
109-
),
11086
migrations.RunPython(
11187
copy_project_field, migrations.RunPython.noop, atomic=False
11288
),
89+
migrations.RunPython(
90+
delete_duplicate_series, migrations.RunPython.noop, atomic=False
91+
),
11392
migrations.AlterField(
11493
model_name='seriesreference',
11594
name='project',
@@ -118,4 +97,7 @@ class Migration(migrations.Migration):
11897
to='patchwork.Project',
11998
),
12099
),
100+
migrations.AlterUniqueTogether(
101+
name='seriesreference', unique_together={('project', 'msgid')}
102+
),
121103
]

0 commit comments

Comments
 (0)