def _insert_patch(self): patch = Patch(project=defaults.project, submitter=defaults.patch_author_person, msgid=defaults.patch_name, content=defaults.patch) patch.save() return patch
def setUp(self): defaults.project.save() self.user = create_maintainer(defaults.project) self.client.login(username=self.user.username, password=self.user.username) self.properties_form_id = 'patchform-properties' self.url = reverse('patchwork.views.patch.list', args=[defaults.project.linkname]) self.base_data = { 'action': 'Update', 'project': str(defaults.project.id), 'form': 'patchlistform', 'archived': '*', 'delegate': '*', 'state': '*' } self.patches = [] for name in ['patch one', 'patch two', 'patch three']: patch = Patch(project=defaults.project, msgid=name, name=name, content='', submitter=Person.objects.get(user=self.user)) patch.save() self.patches.append(patch)
def _insert_patch(self): patch = Patch(project=defaults.project, submitter=defaults.patch_author_person, msgid=make_msgid(), content=defaults.patch, date=self.last_patch_ts) self.last_patch_ts += datetime.timedelta(0, 1) patch.save() return patch
def setUp(self): self.project = defaults.project self.project.send_notifications = True self.project.save() self.submitter = defaults.patch_author_person self.submitter.save() self.patch = Patch(project=self.project, msgid='testpatch', name='testpatch', content='', submitter=self.submitter)
def setUp(self): defaults.project.save() self.person = defaults.patch_author_person self.person.save() self.patch = Patch(project=defaults.project, msgid='p1', name='testpatch', submitter=self.person, content='') self.patch.save()
def setUp(self): project = Project(linkname='test-project', name='Test Project', use_tags=True) project.save() defaults.patch_author_person.save() self.patch = Patch(project=project, msgid='x', name=defaults.patch_name, submitter=defaults.patch_author_person, content='') self.patch.save() self.tagger = 'Test Tagger <*****@*****.**>'
def testList(self): defaults.project.save() defaults.patch_author_person.save() patch = Patch(project=defaults.project, submitter=defaults.patch_author_person, msgid=defaults.patch_name, content=defaults.patch) patch.save() patches = self.rpc.patch_list() self.assertEqual(len(patches), 1) self.assertEqual(patches[0]['id'], patch.id)
def setUp(self): defaults.project.save() defaults.patch_author_person.save() self.patch_content = read_patch(self.patch_filename, encoding=self.patch_encoding) self.patch = Patch(project=defaults.project, msgid='x', name=defaults.patch_name, submitter=defaults.patch_author_person, content=self.patch_content) self.patch.save() self.client = Client()
def setUp(self): defaults.project.save() for (name, email, date) in self.patchmeta: patch_name = 'testpatch' + name person = Person(name=name, email=email) person.save() patch = Patch(project=defaults.project, msgid=patch_name, submitter=person, content='', date=date) patch.save()
def testWithChangedPersonName(self): self.patch = Patch(project=defaults.project, msgid='p1', name='testpatch', submitter=self.person, content='', headers='From: {}'.format(self.original_author)) self.patch.save() response = self.client.get('/patch/%d/mbox/' % self.patch.id) self.assertContains(response, 'From: {}'.format(self.original_author)) self.assertNotContains(response, self.person.name) self.assertNotContains(response, self.person.email)
def testWithoutTheHeaderFallback(self): """ this should fallback to the Person """ self.patch = Patch(project=defaults.project, msgid='p1', name='testpatch', submitter=self.person, content='', headers='') self.patch.save() response = self.client.get('/patch/%d/mbox/' % self.patch.id) self.assertContains( response, 'From: {} <{}>'.format(self.person.name, self.person.email))
def setUp(self): defaults.project.save() self.person = defaults.patch_author_person self.person.save() self.cc_header = 'Cc: CC Person <*****@*****.**>' self.to_header = 'To: To Person <*****@*****.**>' self.date_header = 'Date: Fri, 7 Jun 2013 15:42:54 +1000' self.patch = Patch(project=defaults.project, msgid='p1', name='testpatch', submitter=self.person, content='')
def testPatchSubmitterExpiry(self): defaults.project.save() defaults.patch_author_person.save() # someone submits a patch... patch = Patch(project=defaults.project, msgid='*****@*****.**', name='test patch', submitter=defaults.patch_author_person, content=defaults.patch) patch.save() # ... then starts registration... date = ((datetime.datetime.now() - EmailConfirmation.validity) - datetime.timedelta(hours=1)) userid = 'test-user' user = User.objects.create_user(userid, defaults.patch_author_person.email, userid) user.is_active = False user.date_joined = user.last_login = date user.save() self.assertEqual(user.email, patch.submitter.email) conf = EmailConfirmation(type='registration', user=user, email=user.email) conf.date = date conf.save() # ... which expires do_expiry() # we should see no matching user self.assertFalse( User.objects.filter(email=patch.submitter.email).exists()) # but the patch and person should still be present self.assertTrue( Person.objects.filter(pk=defaults.patch_author_person.pk).exists()) self.assertTrue(Patch.objects.filter(pk=patch.pk).exists()) # and there should be no user associated with the person self.assertEqual( Person.objects.get(pk=defaults.patch_author_person.pk).user, None)
def setUp(self, patch_count=3): patch_names = ['testpatch%d' % (i) for i in range(1, patch_count+1)] self.user = create_user() self.client.login(username = self.user.username, password = self.user.username) defaults.project.save() self.bundle = Bundle(owner = self.user, project = defaults.project, name = 'testbundle') self.bundle.save() self.patches = [] for patch_name in patch_names: patch = Patch(project = defaults.project, msgid = patch_name, name = patch_name, submitter = Person.objects.get(user = self.user), content = '') patch.save() self.patches.append(patch)
def setUp(self): defaults.project.save() self.person = defaults.patch_author_person self.person.save() self.patch = Patch(project=defaults.project, msgid='p1', name='testpatch', submitter=self.person, content='') self.patch.save() self.txt = 'some comment\n---\n some/file | 1 +\n' comment = Comment(patch=self.patch, msgid='p1', submitter=self.person, content=self.txt) comment.save()
def testPullRequestEvent(self): submitter = Person() submitter.save() patch = Patch(project=self.project, pull_url="git://foo.bar master", submitter=submitter) patch.save() # n events for n series, +1 for the pull request above events = self.get_json('/projects/%(project_id)s/events/') self.assertEqual(events['count'], self.n_series + 1) prs = filter(lambda r: r['name'] == 'pull-request-new', events['results']) prs = list(prs) self.assertEqual(len(prs), 1) self.assertEqual(prs[0]['patch'], patch.id) self.assertEqual(prs[0]['parameters']['pull_url'], patch.pull_url)
def testNotificationMerge(self): patches = [ self.patch, Patch(project=self.project, msgid='testpatch-2', name='testpatch 2', content='', submitter=self.submitter) ] for patch in patches: patch.save() PatchChangeNotification(patch=patch, orig_state=patch.state).save() self.assertEqual(PatchChangeNotification.objects.count(), len(patches)) self._expireNotifications() errors = send_notifications() self.assertEqual(errors, []) self.assertEqual(len(mail.outbox), 1) msg = mail.outbox[0] self.assertIn(patches[0].get_absolute_url(), msg.body) self.assertIn(patches[1].get_absolute_url(), msg.body)
def testUnexpiredNotificationMerge(self): """Test that when there are multiple pending notifications, with at least one within the notification delay, that other notifications are held""" patches = [ self.patch, Patch(project=self.project, msgid='testpatch-2', name='testpatch 2', content='', submitter=self.submitter) ] for patch in patches: patch.save() PatchChangeNotification(patch=patch, orig_state=patch.state).save() self.assertEquals(PatchChangeNotification.objects.count(), len(patches)) self._expireNotifications() # update one notification, to bring it out of the notification delay patches[0].state = State.objects.exclude(pk=patches[0].state.pk)[0] patches[0].save() # the updated notification should prevent the other from being sent errors = send_notifications() self.assertEquals(errors, []) self.assertEquals(len(mail.outbox), 0) # expire the updated notification self._expireNotifications() errors = send_notifications() self.assertEquals(errors, []) self.assertEquals(len(mail.outbox), 1) msg = mail.outbox[0] self.assertTrue(patches[0].get_absolute_url() in msg.body) self.assertTrue(patches[1].get_absolute_url() in msg.body)
def setUp(self): defaults.project.save() self.person = defaults.patch_author_person self.person.save() self.patch = Patch(project=defaults.project, msgid='p1', name='testpatch', submitter=self.person, content='') self.patch.save() comment = Comment(patch=self.patch, msgid='p1', submitter=self.person, content='comment 1 text\nAcked-by: 1\n---\nupdate\n') comment.save() comment = Comment(patch=self.patch, msgid='p2', submitter=self.person, content='comment 2 text\nAcked-by: 2\n') comment.save()
def find_content(project, mail): patchbuf = None commentbuf = '' pullurl = None is_attachment = False for part in mail.walk(): if part.get_content_maintype() != 'text': continue payload = part.get_payload(decode=True) subtype = part.get_content_subtype() if not isinstance(payload, unicode): charset = part.get_content_charset() # Check that we have a charset that we understand. Otherwise, # ignore it and fallback to our standard set. if charset is not None: try: codecs.lookup(charset) except LookupError: charset = None # If there is no charset or if it is unknown, then try some common # charsets before we fail. if charset is None: try_charsets = ['utf-8', 'windows-1252', 'iso-8859-1'] else: try_charsets = [charset] for cset in try_charsets: decoded_payload = try_decode(payload, cset) if decoded_payload is not None: break payload = decoded_payload # Could not find a valid decoded payload. Fail. if payload is None: return None if subtype in ['x-patch', 'x-diff']: is_attachment = True patchbuf = payload elif subtype == 'plain': c = payload if not patchbuf: (patchbuf, c) = parse_patch(payload) if not pullurl: pullurl = find_pull_request(payload) if c is not None: commentbuf += c.strip() + '\n' ret = MailContent() drop_prefixes = [project.linkname] + project.get_subject_prefix_tags() (name, prefixes) = clean_subject(mail.get('Subject'), drop_prefixes) (x, n) = parse_series_marker(prefixes) refs = build_references_list(mail) is_root = refs == [] is_cover_letter = is_root and x == 0 is_patch = patchbuf is not None is_git_send_email = mail.get('X-Mailer', '').startswith('git-send-email ') drop_patch = not is_attachment and \ project.git_send_email_only and not is_git_send_email if pullurl or (is_patch and not drop_patch): ret.patch_order = x or 1 ret.patch = Patch(name=name, pull_url=pullurl, content=patchbuf, date=mail_date(mail), headers=mail_headers(mail)) # Create/update the Series and SeriesRevision objects if is_cover_letter or is_patch: msgid = mail.get('Message-Id').strip() # Series get a generic name when they don't start by a cover letter or # when they haven't received the root message yet. Except when it's # only 1 patch, then the series takes the patch subject as name. series_name = None if is_cover_letter or n is None: series_name = strip_prefixes(name) (ret.series, ret.revision, ret.patch_order) = \ find_series_for_mail(project, series_name, msgid, is_patch, ret.patch_order, refs) ret.series.n_patches = n or 1 date = mail_date(mail) if not ret.series.submitted or date < ret.series.submitted: ret.series.submitted = date if is_cover_letter: ret.revision.cover_letter = clean_content(commentbuf) return ret if commentbuf: # If this is a new patch, we defer setting comment.patch until # patch has been saved by the caller if ret.patch: ret.comment = Comment(date=mail_date(mail), content=clean_content(commentbuf), headers=mail_headers(mail)) else: cpatch = find_patch_for_comment(project, refs) if not cpatch: return ret ret.comment = Comment(patch=cpatch, date=mail_date(mail), content=clean_content(commentbuf), headers=mail_headers(mail)) # make sure we always have a valid (series,revision) tuple if we have a # patch. We don't consider pull requests a series. if ret.patch and not pullurl and (not ret.series or not ret.revision): raise Exception("Could not find series for: %s" % name) return ret
def parse_mail(mail, list_id=None): """Parse a mail and add to the database. Args: mail (`mbox.Mail`): Mail to parse and add. list_id (str): Mailing list ID Returns: None """ # some basic sanity checks if 'From' not in mail: raise ValueError("Missing 'From' header") if 'Subject' not in mail: raise ValueError("Missing 'Subject' header") if 'Message-Id' not in mail: raise ValueError("Missing 'Message-Id' header") hint = mail.get('X-Patchwork-Hint', '').lower() if hint == 'ignore': logger.debug("Ignoring email due to 'ignore' hint") return if list_id: project = find_project_by_id(list_id) else: project = find_project_by_header(mail) if project is None: logger.error('Failed to find a project for email') return # parse content diff, message = find_content(project, mail) if not (diff or message): return # nothing to work with msgid = mail.get('Message-Id').strip() author = find_author(mail) subject = mail.get('Subject') name, prefixes = clean_subject(subject, [project.linkname]) is_comment = subject_check(subject) x, n = parse_series_marker(prefixes) refs = find_references(mail) date = find_date(mail) headers = find_headers(mail) pull_url = parse_pull_request(message) # build objects if not is_comment and (diff or pull_url): # patches or pull requests # we delay the saving until we know we have a patch. author.save() delegate = find_delegate(mail) if not delegate and diff: filenames = find_filenames(diff) delegate = auto_delegate(project, filenames) patch = Patch(msgid=msgid, project=project, name=name, date=date, headers=headers, submitter=author, content=message, diff=diff, pull_url=pull_url, delegate=delegate, state=find_state(mail)) patch.save() logger.debug('Patch saved') return patch elif x == 0: # (potential) cover letters # if refs are empty, it's implicitly a cover letter. If not, # however, we need to see if a match already exists and, if # not, assume that it is indeed a new cover letter is_cover_letter = False if not is_comment: if not refs == []: try: CoverLetter.objects.all().get(name=name) except CoverLetter.DoesNotExist: # if no match, this is a new cover letter is_cover_letter = True except CoverLetter.MultipleObjectsReturned: # if multiple cover letters are found, just ignore pass else: is_cover_letter = True if is_cover_letter: author.save() cover_letter = CoverLetter(msgid=msgid, project=project, name=name, date=date, headers=headers, submitter=author, content=message) cover_letter.save() logger.debug('Cover letter saved') return cover_letter # comments # we only save comments if we have the parent email submission = find_submission_for_comment(project, refs) if not submission: return if is_comment and diff: message += diff author.save() comment = Comment(submission=submission, msgid=msgid, date=date, headers=headers, submitter=author, content=message) comment.save() logger.debug('Comment saved') return comment
def parse_mail(mail, list_id=None): """Parse a mail and add to the database. Args: mail (`mbox.Mail`): Mail to parse and add. list_id (str): Mailing list ID Returns: None """ # some basic sanity checks if 'From' not in mail: raise ValueError("Missing 'From' header") if 'Subject' not in mail: raise ValueError("Missing 'Subject' header") if 'Message-Id' not in mail: raise ValueError("Missing 'Message-Id' header") hint = clean_header(mail.get('X-Patchwork-Hint', '')) if hint and hint.lower() == 'ignore': logger.debug("Ignoring email due to 'ignore' hint") return if list_id: project = find_project_by_id(list_id) else: project = find_project_by_header(mail) if project is None: logger.error('Failed to find a project for email') return # parse metadata msgid = clean_header(mail.get('Message-Id')) if not msgid: raise ValueError("Broken 'Message-Id' header") msgid = msgid[:255] author = find_author(mail) subject = mail.get('Subject') name, prefixes = clean_subject(subject, [project.linkname]) is_comment = subject_check(subject) x, n = parse_series_marker(prefixes) version = parse_version(name, prefixes) refs = find_references(mail) date = find_date(mail) headers = find_headers(mail) # parse content if not is_comment: diff, message = find_patch_content(mail) else: diff, message = find_comment_content(mail) if not (diff or message): return # nothing to work with pull_url = parse_pull_request(message) # build objects if not is_comment and (diff or pull_url): # patches or pull requests # we delay the saving until we know we have a patch. author.save() delegate = find_delegate_by_header(mail) if not delegate and diff: filenames = find_filenames(diff) delegate = find_delegate_by_filename(project, filenames) # if we don't have a series marker, we will never have an existing # series to match against. series = None if n: series = find_series(project, mail) else: x = n = 1 # We will create a new series if: # - there is no existing series to assign this patch to, or # - there is an existing series, but it already has a patch with this # number in it if not series or (SeriesPatch.objects.filter(series=series, number=x).count()): series = Series(project=project, date=date, submitter=author, version=version, total=n) series.save() # NOTE(stephenfin) We must save references for series. We # do this to handle the case where a later patch is # received first. Without storing references, it would not # be possible to identify the relationship between patches # as the earlier patch does not reference the later one. for ref in refs + [msgid]: ref = ref[:255] # we don't want duplicates try: # we could have a ref to a previous series. (For # example, a series sent in reply to another # series.) That should not create a series ref # for this series, so check for the msg-id only, # not the msg-id/series pair. SeriesReference.objects.get(msgid=ref, series__project=project) except SeriesReference.DoesNotExist: SeriesReference.objects.create(series=series, msgid=ref) patch = Patch(msgid=msgid, project=project, name=name[:255], date=date, headers=headers, submitter=author, content=message, diff=diff, pull_url=pull_url, delegate=delegate, state=find_state(mail)) patch.save() logger.debug('Patch saved') # add to a series if we have found one, and we have a numbered # patch. Don't add unnumbered patches (for example diffs sent # in reply, or just messages with random refs/in-reply-tos) if series and x: series.add_patch(patch, x) return patch elif x == 0: # (potential) cover letters # if refs are empty, it's implicitly a cover letter. If not, # however, we need to see if a match already exists and, if # not, assume that it is indeed a new cover letter is_cover_letter = False if not is_comment: if not refs == []: try: CoverLetter.objects.all().get(name=name) except CoverLetter.DoesNotExist: # if no match, this is a new cover letter is_cover_letter = True except CoverLetter.MultipleObjectsReturned: # if multiple cover letters are found, just ignore pass else: is_cover_letter = True if is_cover_letter: author.save() # we don't use 'find_series' here as a cover letter will # always be the first item in a thread, thus the references # could only point to a different series or unrelated # message try: series = SeriesReference.objects.get( msgid=msgid, series__project=project).series except SeriesReference.DoesNotExist: series = None if not series: series = Series(project=project, date=date, submitter=author, version=version, total=n) series.save() # we don't save the in-reply-to or references fields # for a cover letter, as they can't refer to the same # series SeriesReference.objects.get_or_create(series=series, msgid=msgid) cover_letter = CoverLetter(msgid=msgid, project=project, name=name[:255], date=date, headers=headers, submitter=author, content=message) cover_letter.save() logger.debug('Cover letter saved') series.add_cover_letter(cover_letter) return cover_letter # comments # we only save comments if we have the parent email submission = find_submission_for_comment(project, refs) if not submission: return author.save() comment = Comment(submission=submission, msgid=msgid, date=date, headers=headers, submitter=author, content=message) comment.save() logger.debug('Comment saved') return comment