@@ -33,8 +33,11 @@ import logging
from patchwork.parser import parse_patch
from patchwork.models import Patch, Project, Person, Comment, State, Series, \
- SeriesRevision, SeriesRevisionPatch, get_default_initial_patch_state
+ SeriesRevision, SeriesRevisionPatch, get_default_initial_patch_state, \
+ series_revision_complete
import django
+from django import dispatch
+from django.db.models import Q
from django.conf import settings
from django.contrib.auth.models import User
from django.core.exceptions import MultipleObjectsReturned
@@ -558,6 +561,43 @@ def get_delegate(delegate_email):
pass
return None
+series_name_re = re.compile('[, \(]*(v|take)[\) 0-9]+$', re.I)
+def clean_series_name(str):
+ """Try to remove 'v2' and 'take 28' markers in cover letters subjects"""
+ str = series_name_re.sub('', str)
+ return str.strip()
+
+def on_revision_complete(sender, revision, **kwargs):
+ # Brand new series (revision.version == 1) may be updates to a Series
+ # previously posted. Hook the SeriesRevision to the previous series then.
+ if revision.version != 1:
+ return
+
+ new_series = revision.series
+ if new_series.name == 'Untitled series':
+ return
+
+ name = clean_series_name(new_series.name)
+ previous_series = Series.objects.filter(Q(project=new_series.project),
+ Q(name__iexact=name) &
+ ~Q(pk=new_series.pk))
+ if len(previous_series) != 1:
+ return
+
+
+ previous_series = previous_series[0]
+ new_revision = previous_series.latest_revision().duplicate_meta()
+ new_revision.root_msgid = revision.root_msgid
+ new_revision.cover_letter = revision.cover_letter
+ new_revision.save()
+ i = 1
+ for patch in revision.ordered_patches():
+ new_revision.add_patch(patch, i)
+ i += 1
+
+ revision.delete()
+ new_series.delete()
+
def parse_mail(mail):
# some basic sanity checks
@@ -591,6 +631,8 @@ def parse_mail(mail):
series = content.series
revision = content.revision
+ series_revision_complete.connect(on_revision_complete)
+
if series:
if save_required:
author.save()
@@ -628,6 +670,8 @@ def parse_mail(mail):
comment.msgid = msgid
comment.save()
+ series_revision_complete.disconnect(on_revision_complete)
+
return 0
extra_error_message = '''
@@ -24,7 +24,8 @@ from patchwork.models import Patch, Series, SeriesRevision, Project
from patchwork.tests.utils import read_mail
from patchwork.tests.utils import defaults, read_mail, TestSeries
-from patchwork.bin.parsemail import parse_mail, build_references_list
+from patchwork.bin.parsemail import parse_mail, build_references_list, \
+ clean_series_name
class SeriesTest(TestCase):
fixtures = ['default_states']
@@ -456,3 +457,73 @@ class SinglePatchUpdatesVariousCornerCasesTest(TestCase):
self.assertEqual(len(patches), 2)
self.assertEqual(patches[0].name, '[1/2] ' + defaults.patch_name)
self.assertEqual(patches[1].name, '[v2] ' + defaults.patch_name)
+
+#
+# New version of a full series (separate mail thread)
+#
+
+class FullSeriesUpdateTest(GeneratedSeriesTest):
+
+ def check_revision(self, series, revision, mails):
+ n_patches = len(mails)
+ if self.has_cover_letter:
+ n_patches -= 1
+
+ self.assertEquals(revision.series_id, series.id)
+ self.assertEquals(revision.root_msgid, mails[0].get('Message-Id'))
+ self.assertEquals(revision.patches.count(), n_patches)
+
+ i = 1 if self.has_cover_letter else 0
+ for patch in revision.ordered_patches():
+ patch_mail = mails[i]
+ self.assertEquals(patch.msgid, patch_mail.get('Message-Id'))
+ i += 1
+
+ def check(self, series1_mails, series2_mails):
+ self.assertEquals(Series.objects.count(), 1)
+ series = Series.objects.all()[0]
+ self.assertEquals(series.version, 2)
+
+ revisions = SeriesRevision.objects.all()
+ self.assertEquals(revisions.count(), 2)
+
+ self.check_revision(series, revisions[0], series1_mails)
+ self.check_revision(series, revisions[1], series2_mails)
+
+ def _set_cover_letter_subject(self, mail, n_patches, subject):
+ del mail['Subject']
+ mail['Subject'] = '[PATCH 0/%d] %s' % (n_patches, subject)
+
+ def _test_internal(self, n_patches, subjects):
+
+ (series1, series1_mails) = self._create_series(n_patches)
+ self._set_cover_letter_subject(series1_mails[0], n_patches, subjects[0])
+ self.series_name = subjects[0]
+ series1.insert(series1_mails)
+ self.commonInsertionChecks()
+
+ (series2,series2_mails) = self._create_series(n_patches)
+ self._set_cover_letter_subject(series2_mails[0], n_patches, subjects[1])
+ series2.insert(series2_mails)
+
+ self.check(series1_mails, series2_mails)
+
+ def testCleanSeriesName(self):
+ cases = (
+ ('Awesome series', 'Awesome series'),
+ ('Awesome series', 'Awesome series v2'),
+ ('Awesome series', 'Awesome series V2'),
+ ('Awesome series', 'Awesome series, v3'),
+ ('Awesome series', 'Awesome series (v4)'),
+ ('Awesome series', 'Awesome series (take 5)'),
+ ('Awesome series', 'Awesome series (Take 5)'),
+ ('Awesome series', 'Awesome series, take 6'),
+ )
+ for case in cases:
+ self.assertEquals(clean_series_name(case[1]), case[0])
+
+ def testNewSeries(self):
+ self._test_internal(3, ('Awesome series', 'Awesome series (v4)'))
+
+ def testNewSeriesIgnoreCase(self):
+ self._test_internal(3, ('Awesome series', 'awesome series (V4)'))
There's another way than the one already implemented to update a series: resend another full thread with some of the patches changed. Because those two mail threads are separate, the only way is to try and match the subject of their cover letters. v2: Rebase on top of single patch update changes Signed-off-by: Damien Lespiau <damien.lespiau@intel.com> --- patchwork/bin/parsemail.py | 46 +++++++++++++++++++++++++- patchwork/tests/test_series.py | 73 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 2 deletions(-)