Exemplo n.º 1
0
 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
Exemplo n.º 2
0
 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)
Exemplo n.º 3
0
 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
Exemplo n.º 4
0
 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)
Exemplo n.º 5
0
    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()
Exemplo n.º 6
0
 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 <*****@*****.**>'
Exemplo n.º 7
0
    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)
Exemplo n.º 8
0
 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()
Exemplo n.º 9
0
    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()
Exemplo n.º 10
0
    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)
Exemplo n.º 11
0
    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))
Exemplo n.º 12
0
    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='')
Exemplo n.º 13
0
    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)
Exemplo n.º 14
0
    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)
Exemplo n.º 15
0
    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()
Exemplo n.º 16
0
    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)
Exemplo n.º 17
0
    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)
Exemplo n.º 18
0
    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)
Exemplo n.º 19
0
    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()
Exemplo n.º 20
0
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
Exemplo n.º 21
0
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
Exemplo n.º 22
0
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