Changeset - fdde16d7cea0
[Not reviewed]
default
0 6 0
Mads Kiilerich - 6 years ago 2020-02-29 16:06:30
mads@kiilerich.com
Grafted from: bc10216c34c7
celery: fix send_email to work with JSON encoding (Issue #363)

Long time ago, c935bcaf7086 introduced an optional User object parameter to the
send_email task and used the computed full_name_or_username property. Due to the
magic of pickle, that also worked when using Celery to run the task async.

Now, Celery 4 changed the default encoding from Pickle to JSON, which we
anticipated in e539db6cc0da. That broke send_email in some cases, for example
when a user comments on another user's changeset.

Fixed by passing the "From" name as string instead of passing the whole User
object.

Thanks to vyom for reporting.
6 files changed with 34 insertions and 32 deletions:
0 comments (0 inline, 0 general)
kallithea/lib/celerylib/tasks.py
Show inline comments
 
@@ -188,145 +188,146 @@ def get_commits_stats(repo_name, ts_min_
 

	
 
        if not co_day_auth_aggr:
 
            co_day_auth_aggr[akc(repo.contact)] = {
 
                "label": akc(repo.contact),
 
                "data": [0, 1],
 
                "schema": ["commits"],
 
            }
 

	
 
        stats = cur_stats if cur_stats else Statistics()
 
        stats.commit_activity = ascii_bytes(ext_json.dumps(co_day_auth_aggr))
 
        stats.commit_activity_combined = ascii_bytes(ext_json.dumps(overview_data))
 

	
 
        log.debug('last revision %s', last_rev)
 
        leftovers = len(repo.revisions[last_rev:])
 
        log.debug('revisions to parse %s', leftovers)
 

	
 
        if last_rev == 0 or leftovers < parse_limit:
 
            log.debug('getting code trending stats')
 
            stats.languages = ascii_bytes(ext_json.dumps(__get_codes_stats(repo_name)))
 

	
 
        try:
 
            stats.repository = dbrepo
 
            stats.stat_on_revision = last_cs.revision if last_cs else 0
 
            DBS.add(stats)
 
            DBS.commit()
 
        except:
 
            log.error(traceback.format_exc())
 
            DBS.rollback()
 
            lock.release()
 
            return False
 

	
 
        # final release
 
        lock.release()
 

	
 
        # execute another task if celery is enabled
 
        if len(repo.revisions) > 1 and kallithea.CELERY_APP and recurse_limit > 0:
 
            get_commits_stats(repo_name, ts_min_y, ts_max_y, recurse_limit - 1)
 
        elif recurse_limit <= 0:
 
            log.debug('Not recursing - limit has been reached')
 
        else:
 
            log.debug('Not recursing')
 
    except celerylib.LockHeld:
 
        log.info('Task with key %s already running', lockkey)
 
        return 'Task with key %s already running' % lockkey
 

	
 

	
 
@celerylib.task
 
@celerylib.dbsession
 
def send_email(recipients, subject, body='', html_body='', headers=None, author=None):
 
def send_email(recipients, subject, body='', html_body='', headers=None, from_name=None):
 
    """
 
    Sends an email with defined parameters from the .ini files.
 

	
 
    :param recipients: list of recipients, if this is None, the defined email
 
        address from field 'email_to' and all admins is used instead
 
    :param subject: subject of the mail
 
    :param body: body of the mail
 
    :param html_body: html version of body
 
    :param headers: dictionary of prepopulated e-mail headers
 
    :param author: User object of the author of this mail, if known and relevant
 
    :param from_name: full name to be used as sender of this mail - often a
 
    .full_name_or_username value
 
    """
 
    assert isinstance(recipients, list), recipients
 
    if headers is None:
 
        headers = {}
 
    else:
 
        # do not modify the original headers object passed by the caller
 
        headers = headers.copy()
 

	
 
    email_config = config
 
    email_prefix = email_config.get('email_prefix', '')
 
    if email_prefix:
 
        subject = "%s %s" % (email_prefix, subject)
 

	
 
    if not recipients:
 
        # if recipients are not defined we send to email_config + all admins
 
        recipients = [u.email for u in User.query()
 
                      .filter(User.admin == True).all()]
 
        if email_config.get('email_to') is not None:
 
            recipients += email_config.get('email_to').split(',')
 

	
 
        # If there are still no recipients, there are no admins and no address
 
        # configured in email_to, so return.
 
        if not recipients:
 
            log.error("No recipients specified and no fallback available.")
 
            return False
 

	
 
        log.warning("No recipients specified for '%s' - sending to admins %s", subject, ' '.join(recipients))
 

	
 
    # SMTP sender
 
    envelope_from = email_config.get('app_email_from', 'Kallithea')
 
    # 'From' header
 
    if author is not None:
 
        # set From header based on author but with a generic e-mail address
 
    if from_name is not None:
 
        # set From header based on from_name but with a generic e-mail address
 
        # In case app_email_from is in "Some Name <e-mail>" format, we first
 
        # extract the e-mail address.
 
        envelope_addr = author_email(envelope_from)
 
        headers['From'] = '"%s" <%s>' % (
 
            email.utils.quote('%s (no-reply)' % author.full_name_or_username),
 
            email.utils.quote('%s (no-reply)' % from_name),
 
            envelope_addr)
 

	
 
    user = email_config.get('smtp_username')
 
    passwd = email_config.get('smtp_password')
 
    mail_server = email_config.get('smtp_server')
 
    mail_port = email_config.get('smtp_port')
 
    tls = str2bool(email_config.get('smtp_use_tls'))
 
    ssl = str2bool(email_config.get('smtp_use_ssl'))
 
    debug = str2bool(email_config.get('debug'))
 
    smtp_auth = email_config.get('smtp_auth')
 

	
 
    logmsg = ("Mail details:\n"
 
              "recipients: %s\n"
 
              "headers: %s\n"
 
              "subject: %s\n"
 
              "body:\n%s\n"
 
              "html:\n%s\n"
 
              % (' '.join(recipients), headers, subject, body, html_body))
 

	
 
    if mail_server:
 
        log.debug("Sending e-mail. " + logmsg)
 
    else:
 
        log.error("SMTP mail server not configured - cannot send e-mail.")
 
        log.warning(logmsg)
 
        return False
 

	
 
    try:
 
        m = SmtpMailer(envelope_from, user, passwd, mail_server, smtp_auth,
 
                       mail_port, ssl, tls, debug=debug)
 
        m.send(recipients, subject, body, html_body, headers=headers)
 
    except:
 
        log.error('Mail sending failed')
 
        log.error(traceback.format_exc())
 
        return False
 
    return True
 

	
 

	
 
@celerylib.task
 
@celerylib.dbsession
 
def create_repo(form_data, cur_user):
 
    from kallithea.model.repo import RepoModel
 
    from kallithea.model.db import Setting
 

	
 
    DBS = celerylib.get_session()
 

	
 
    cur_user = User.guess_instance(cur_user)
 

	
 
    owner = cur_user
kallithea/model/notification.py
Show inline comments
 
@@ -87,97 +87,98 @@ class NotificationModel(object):
 
            )
 
        elif recipients is None:
 
            # empty recipients means to all admins
 
            recipients_objs = User.query().filter(User.admin == True).all()
 
            log.debug('sending notifications %s to admins: %s',
 
                type_, recipients_objs
 
            )
 
        #else: silently skip notification mails?
 

	
 
        if not with_email:
 
            return
 

	
 
        headers = {}
 
        headers['X-Kallithea-Notification-Type'] = type_
 
        if 'threading' in email_kwargs:
 
            headers['References'] = ' '.join('<%s>' % x for x in email_kwargs['threading'])
 

	
 
        # this is passed into template
 
        created_on = h.fmt_date(datetime.datetime.now())
 
        html_kwargs = {
 
                  'subject': subject,
 
                  'body': h.render_w_mentions(body, repo_name),
 
                  'when': created_on,
 
                  'user': created_by_obj.username,
 
                  }
 

	
 
        txt_kwargs = {
 
                  'subject': subject,
 
                  'body': body,
 
                  'when': created_on,
 
                  'user': created_by_obj.username,
 
                  }
 

	
 
        html_kwargs.update(email_kwargs)
 
        txt_kwargs.update(email_kwargs)
 
        email_subject = EmailNotificationModel() \
 
                            .get_email_description(type_, **txt_kwargs)
 
        email_txt_body = EmailNotificationModel() \
 
                            .get_email_tmpl(type_, 'txt', **txt_kwargs)
 
        email_html_body = EmailNotificationModel() \
 
                            .get_email_tmpl(type_, 'html', **html_kwargs)
 

	
 
        # don't send email to person who created this comment
 
        rec_objs = set(recipients_objs).difference(set([created_by_obj]))
 

	
 
        # send email with notification to all other participants
 
        for rec in rec_objs:
 
            tasks.send_email([rec.email], email_subject, email_txt_body,
 
                     email_html_body, headers, author=created_by_obj)
 
                     email_html_body, headers,
 
                     from_name=created_by_obj.full_name_or_username)
 

	
 

	
 
class EmailNotificationModel(object):
 

	
 
    TYPE_CHANGESET_COMMENT = NotificationModel.TYPE_CHANGESET_COMMENT
 
    TYPE_MESSAGE = NotificationModel.TYPE_MESSAGE # only used for testing
 
    # NotificationModel.TYPE_MENTION is not used
 
    TYPE_PASSWORD_RESET = 'password_link'
 
    TYPE_REGISTRATION = NotificationModel.TYPE_REGISTRATION
 
    TYPE_PULL_REQUEST = NotificationModel.TYPE_PULL_REQUEST
 
    TYPE_PULL_REQUEST_COMMENT = NotificationModel.TYPE_PULL_REQUEST_COMMENT
 
    TYPE_DEFAULT = 'default'
 

	
 
    def __init__(self):
 
        super(EmailNotificationModel, self).__init__()
 
        self._tmpl_lookup = app_globals.mako_lookup
 
        self.email_types = {
 
            self.TYPE_CHANGESET_COMMENT: 'changeset_comment',
 
            self.TYPE_PASSWORD_RESET: 'password_reset',
 
            self.TYPE_REGISTRATION: 'registration',
 
            self.TYPE_DEFAULT: 'default',
 
            self.TYPE_PULL_REQUEST: 'pull_request',
 
            self.TYPE_PULL_REQUEST_COMMENT: 'pull_request_comment',
 
        }
 
        self._subj_map = {
 
            self.TYPE_CHANGESET_COMMENT: _('[Comment] %(repo_name)s changeset %(short_id)s "%(message_short)s" on %(branch)s'),
 
            self.TYPE_MESSAGE: 'Test Message',
 
            # self.TYPE_PASSWORD_RESET
 
            self.TYPE_REGISTRATION: _('New user %(new_username)s registered'),
 
            # self.TYPE_DEFAULT
 
            self.TYPE_PULL_REQUEST: _('[Review] %(repo_name)s PR %(pr_nice_id)s "%(pr_title_short)s" from %(pr_source_branch)s by %(pr_owner_username)s'),
 
            self.TYPE_PULL_REQUEST_COMMENT: _('[Comment] %(repo_name)s PR %(pr_nice_id)s "%(pr_title_short)s" from %(pr_source_branch)s by %(pr_owner_username)s'),
 
        }
 

	
 
    def get_email_description(self, type_, **kwargs):
 
        """
 
        return subject for email based on given type
 
        """
 
        tmpl = self._subj_map[type_]
 
        try:
 
            subj = tmpl % kwargs
 
        except KeyError as e:
 
            log.error('error generating email subject for %r from %s: %s', type_, ', '.join(self._subj_map), e)
 
            raise
 
        # gmail doesn't do proper threading but will ignore leading square
 
        # bracket content ... so that is where we put status info
 
        bracket_tags = []
 
        status_change = kwargs.get('status_change')
kallithea/tests/functional/test_login.py
Show inline comments
 
@@ -365,97 +365,97 @@ class TestLoginController(base.TestContr
 
        ret = Session().query(User).filter(User.username == 'test_regular4').one()
 
        assert ret.username == username
 
        assert check_password(password, ret.password) == True
 
        assert ret.email == email
 
        assert ret.name == name
 
        assert ret.lastname == lastname
 
        assert ret.api_key is not None
 
        assert ret.admin == False
 

	
 
    #==========================================================================
 
    # PASSWORD RESET
 
    #==========================================================================
 

	
 
    def test_forgot_password_wrong_mail(self):
 
        bad_email = 'username%wrongmail.org'
 
        response = self.app.post(
 
                        base.url(controller='login', action='password_reset'),
 
                            {'email': bad_email,
 
                             '_session_csrf_secret_token': self.session_csrf_secret_token()})
 

	
 
        response.mustcontain('An email address must contain a single @')
 

	
 
    def test_forgot_password(self):
 
        response = self.app.get(base.url(controller='login',
 
                                    action='password_reset'))
 
        assert response.status == '200 OK'
 

	
 
        username = 'test_password_reset_1'
 
        password = 'qweqwe'
 
        email = 'username@example.com'
 
        name = 'passwd'
 
        lastname = 'reset'
 
        timestamp = int(time.time())
 

	
 
        new = User()
 
        new.username = username
 
        new.password = password
 
        new.email = email
 
        new.name = name
 
        new.lastname = lastname
 
        new.api_key = generate_api_key()
 
        Session().add(new)
 
        Session().commit()
 

	
 
        token = UserModel().get_reset_password_token(
 
            User.get_by_username(username), timestamp, self.session_csrf_secret_token())
 

	
 
        collected = []
 
        def mock_send_email(recipients, subject, body='', html_body='', headers=None, author=None):
 
        def mock_send_email(recipients, subject, body='', html_body='', headers=None, from_name=None):
 
            collected.append((recipients, subject, body, html_body))
 

	
 
        with mock.patch.object(kallithea.lib.celerylib.tasks, 'send_email', mock_send_email), \
 
                mock.patch.object(time, 'time', lambda: timestamp):
 
            response = self.app.post(base.url(controller='login',
 
                                         action='password_reset'),
 
                                     {'email': email,
 
                                      '_session_csrf_secret_token': self.session_csrf_secret_token()})
 

	
 
        self.checkSessionFlash(response, 'A password reset confirmation code has been sent')
 

	
 
        ((recipients, subject, body, html_body),) = collected
 
        assert recipients == ['username@example.com']
 
        assert subject == 'Password reset link'
 
        assert '\n%s\n' % token in body
 
        (confirmation_url,) = (line for line in body.splitlines() if line.startswith('http://'))
 
        assert ' href="%s"' % confirmation_url.replace('&', '&amp;').replace('@', '%40') in html_body
 

	
 
        d = urllib.parse.parse_qs(urllib.parse.urlparse(confirmation_url).query)
 
        assert d['token'] == [token]
 
        assert d['timestamp'] == [str(timestamp)]
 
        assert d['email'] == [email]
 

	
 
        response = response.follow()
 

	
 
        # BAD TOKEN
 

	
 
        bad_token = "bad"
 

	
 
        response = self.app.post(base.url(controller='login',
 
                                     action='password_reset_confirmation'),
 
                                 {'email': email,
 
                                  'timestamp': timestamp,
 
                                  'password': "p@ssw0rd",
 
                                  'password_confirm': "p@ssw0rd",
 
                                  'token': bad_token,
 
                                  '_session_csrf_secret_token': self.session_csrf_secret_token(),
 
                                 })
 
        assert response.status == '200 OK'
 
        response.mustcontain('Invalid password reset token')
 

	
 
        # GOOD TOKEN
 

	
 
        response = self.app.get(confirmation_url)
 
        assert response.status == '200 OK'
 
        response.mustcontain("You are about to set a new password for the email address %s" % email)
 
        response.mustcontain('<form action="%s" method="post">' % base.url(controller='login', action='password_reset_confirmation'))
 
        response.mustcontain('value="%s"' % self.session_csrf_secret_token())
kallithea/tests/models/test_dump_html_mails.ref.html
Show inline comments
 
<!doctype html>
 
<html lang="en">
 
<head><title>Notifications</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head>
 
<body>
 
<hr/>
 
<h1>cs_comment, is_mention=False, status_change=None</h1>
 
<pre>
 
From: u1
 
From: u1 u1 <name@example.com>
 
To: u2@example.com
 
Subject: [Comment] repo/name changeset cafe1234 "This changeset did something cl..." on brunch
 
</pre>
 
<hr/>
 
<pre>http://comment.org
 

	
 
Comment on Changeset "This changeset did something clever which is hard to explain"
 

	
 

	
 
Opinionated User (jsmith):
 

	
 
This is the new 'comment'.
 

	
 
 - and here it ends indented.
 

	
 

	
 
Changeset on http://example.com/repo_target branch brunch:
 
"This changeset did something clever which is hard to explain" by u2 u3 (u2).
 

	
 

	
 
View Comment: http://comment.org
 
</pre>
 
<hr/>
 
<!--!doctype html-->
 
<!--html lang="en"-->
 
<!--head-->
 
    <!--title--><!--/title-->
 
    <!--meta name="viewport" content="width=device-width"-->
 
    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
 
<!--/head-->
 
<!--body-->
 
<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td>
 
            <table width="100%" cellpadding="0" cellspacing="0" border="0"
 
                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
 
                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
 
                <tr>
 
                    <td colspan="3">
 
<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
 
       style="border-bottom:1px solid #ddd">
 
    <tr>
 
        <td height="20px" style="height:20px" colspan="3"></td>
 
    </tr>
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
 
@@ -119,97 +119,97 @@ View Comment: http://comment.org
 
            <div>
 
                "<a style="color:#395fa0;text-decoration:none"
 
                   href="http://changeset.com">This changeset did something clever which is hard to explain</a>"
 
                by
 
                <span style="border:#ddd 1px solid;background:#f9f9f9">u2 u3 (u2)</span>.
 
            </div>
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
<center>
 
    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
 
        <tr>
 
            <td height="25px" style="height:25px"></td>
 
        </tr>
 
        <tr>
 
            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
 
                <a href="http://comment.org" style="text-decoration:none;display:block" target="_blank">
 
                    <center>
 
                        <font size="3">
 
                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
 
                        </font>
 
                    </center>
 
                </a>
 
            </td>
 
        </tr>
 
    </table>
 
</center>
 
        </td>
 
    </tr>
 
</table>
 
                    </td>
 
                    <td></td>
 
                </tr>
 
                <tr>
 
                    <td height="30px" style="height:30px" colspan="3"></td>
 
                </tr>
 
            </table>
 
        </td>
 
        <td width="30px" style="width:30px"></td>
 
    </tr>
 
</table>
 
<!--/body-->
 
<!--/html-->
 
<hr/>
 
<hr/>
 
<h1>cs_comment, is_mention=True, status_change=None</h1>
 
<pre>
 
From: u1
 
From: u1 u1 <name@example.com>
 
To: u2@example.com
 
Subject: [Comment] repo/name changeset cafe1234 "This changeset did something cl..." on brunch
 
</pre>
 
<hr/>
 
<pre>http://comment.org
 

	
 
Mention in Comment on Changeset "This changeset did something clever which is hard to explain"
 

	
 

	
 
Opinionated User (jsmith):
 

	
 
This is the new 'comment'.
 

	
 
 - and here it ends indented.
 

	
 

	
 
Changeset on http://example.com/repo_target branch brunch:
 
"This changeset did something clever which is hard to explain" by u2 u3 (u2).
 

	
 

	
 
View Comment: http://comment.org
 
</pre>
 
<hr/>
 
<!--!doctype html-->
 
<!--html lang="en"-->
 
<!--head-->
 
    <!--title--><!--/title-->
 
    <!--meta name="viewport" content="width=device-width"-->
 
    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
 
<!--/head-->
 
<!--body-->
 
<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td>
 
            <table width="100%" cellpadding="0" cellspacing="0" border="0"
 
                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
 
                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
 
                <tr>
 
                    <td colspan="3">
 
<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
 
       style="border-bottom:1px solid #ddd">
 
    <tr>
 
        <td height="20px" style="height:20px" colspan="3"></td>
 
    </tr>
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
 
@@ -278,97 +278,97 @@ View Comment: http://comment.org
 
            <div>
 
                "<a style="color:#395fa0;text-decoration:none"
 
                   href="http://changeset.com">This changeset did something clever which is hard to explain</a>"
 
                by
 
                <span style="border:#ddd 1px solid;background:#f9f9f9">u2 u3 (u2)</span>.
 
            </div>
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
<center>
 
    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
 
        <tr>
 
            <td height="25px" style="height:25px"></td>
 
        </tr>
 
        <tr>
 
            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
 
                <a href="http://comment.org" style="text-decoration:none;display:block" target="_blank">
 
                    <center>
 
                        <font size="3">
 
                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
 
                        </font>
 
                    </center>
 
                </a>
 
            </td>
 
        </tr>
 
    </table>
 
</center>
 
        </td>
 
    </tr>
 
</table>
 
                    </td>
 
                    <td></td>
 
                </tr>
 
                <tr>
 
                    <td height="30px" style="height:30px" colspan="3"></td>
 
                </tr>
 
            </table>
 
        </td>
 
        <td width="30px" style="width:30px"></td>
 
    </tr>
 
</table>
 
<!--/body-->
 
<!--/html-->
 
<hr/>
 
<hr/>
 
<h1>cs_comment, is_mention=False, status_change='Approved'</h1>
 
<pre>
 
From: u1
 
From: u1 u1 <name@example.com>
 
To: u2@example.com
 
Subject: [Approved: Comment] repo/name changeset cafe1234 "This changeset did something cl..." on brunch
 
</pre>
 
<hr/>
 
<pre>http://comment.org
 

	
 
Comment on Changeset "This changeset did something clever which is hard to explain"
 

	
 

	
 
Opinionated User (jsmith):
 

	
 
Status change: Approved
 

	
 
This is the new 'comment'.
 

	
 
 - and here it ends indented.
 

	
 

	
 
Changeset on http://example.com/repo_target branch brunch:
 
"This changeset did something clever which is hard to explain" by u2 u3 (u2).
 

	
 

	
 
View Comment: http://comment.org
 
</pre>
 
<hr/>
 
<!--!doctype html-->
 
<!--html lang="en"-->
 
<!--head-->
 
    <!--title--><!--/title-->
 
    <!--meta name="viewport" content="width=device-width"-->
 
    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
 
<!--/head-->
 
<!--body-->
 
<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td>
 
            <table width="100%" cellpadding="0" cellspacing="0" border="0"
 
                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
 
                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
 
                <tr>
 
                    <td colspan="3">
 
<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
 
       style="border-bottom:1px solid #ddd">
 
    <tr>
 
        <td height="20px" style="height:20px" colspan="3"></td>
 
    </tr>
 
    <tr>
 
@@ -455,97 +455,97 @@ View Comment: http://comment.org
 
            <div>
 
                "<a style="color:#395fa0;text-decoration:none"
 
                   href="http://changeset.com">This changeset did something clever which is hard to explain</a>"
 
                by
 
                <span style="border:#ddd 1px solid;background:#f9f9f9">u2 u3 (u2)</span>.
 
            </div>
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
<center>
 
    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
 
        <tr>
 
            <td height="25px" style="height:25px"></td>
 
        </tr>
 
        <tr>
 
            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
 
                <a href="http://comment.org" style="text-decoration:none;display:block" target="_blank">
 
                    <center>
 
                        <font size="3">
 
                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
 
                        </font>
 
                    </center>
 
                </a>
 
            </td>
 
        </tr>
 
    </table>
 
</center>
 
        </td>
 
    </tr>
 
</table>
 
                    </td>
 
                    <td></td>
 
                </tr>
 
                <tr>
 
                    <td height="30px" style="height:30px" colspan="3"></td>
 
                </tr>
 
            </table>
 
        </td>
 
        <td width="30px" style="width:30px"></td>
 
    </tr>
 
</table>
 
<!--/body-->
 
<!--/html-->
 
<hr/>
 
<hr/>
 
<h1>cs_comment, is_mention=True, status_change='Approved'</h1>
 
<pre>
 
From: u1
 
From: u1 u1 <name@example.com>
 
To: u2@example.com
 
Subject: [Approved: Comment] repo/name changeset cafe1234 "This changeset did something cl..." on brunch
 
</pre>
 
<hr/>
 
<pre>http://comment.org
 

	
 
Mention in Comment on Changeset "This changeset did something clever which is hard to explain"
 

	
 

	
 
Opinionated User (jsmith):
 

	
 
Status change: Approved
 

	
 
This is the new 'comment'.
 

	
 
 - and here it ends indented.
 

	
 

	
 
Changeset on http://example.com/repo_target branch brunch:
 
"This changeset did something clever which is hard to explain" by u2 u3 (u2).
 

	
 

	
 
View Comment: http://comment.org
 
</pre>
 
<hr/>
 
<!--!doctype html-->
 
<!--html lang="en"-->
 
<!--head-->
 
    <!--title--><!--/title-->
 
    <!--meta name="viewport" content="width=device-width"-->
 
    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
 
<!--/head-->
 
<!--body-->
 
<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td>
 
            <table width="100%" cellpadding="0" cellspacing="0" border="0"
 
                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
 
                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
 
                <tr>
 
                    <td colspan="3">
 
<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
 
       style="border-bottom:1px solid #ddd">
 
    <tr>
 
        <td height="20px" style="height:20px" colspan="3"></td>
 
    </tr>
 
    <tr>
 
@@ -632,168 +632,168 @@ View Comment: http://comment.org
 
            <div>
 
                "<a style="color:#395fa0;text-decoration:none"
 
                   href="http://changeset.com">This changeset did something clever which is hard to explain</a>"
 
                by
 
                <span style="border:#ddd 1px solid;background:#f9f9f9">u2 u3 (u2)</span>.
 
            </div>
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
<center>
 
    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
 
        <tr>
 
            <td height="25px" style="height:25px"></td>
 
        </tr>
 
        <tr>
 
            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
 
                <a href="http://comment.org" style="text-decoration:none;display:block" target="_blank">
 
                    <center>
 
                        <font size="3">
 
                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
 
                        </font>
 
                    </center>
 
                </a>
 
            </td>
 
        </tr>
 
    </table>
 
</center>
 
        </td>
 
    </tr>
 
</table>
 
                    </td>
 
                    <td></td>
 
                </tr>
 
                <tr>
 
                    <td height="30px" style="height:30px" colspan="3"></td>
 
                </tr>
 
            </table>
 
        </td>
 
        <td width="30px" style="width:30px"></td>
 
    </tr>
 
</table>
 
<!--/body-->
 
<!--/html-->
 
<hr/>
 
<hr/>
 
<h1>message</h1>
 
<pre>
 
From: u1
 
From: u1 u1 <name@example.com>
 
To: u2@example.com
 
Subject: Test Message
 
</pre>
 
<hr/>
 
<pre>This is the 'body' of the "test" message
 
 - nothing interesting here except indentation.</pre>
 
<hr/>
 
<!--!doctype html-->
 
<!--html lang="en"-->
 
<!--head-->
 
    <!--title--><!--/title-->
 
    <!--meta name="viewport" content="width=device-width"-->
 
    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
 
<!--/head-->
 
<!--body-->
 
<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td>
 
            <table width="100%" cellpadding="0" cellspacing="0" border="0"
 
                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
 
                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
 
                <tr>
 
                    <td colspan="3">
 
<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
 
       style="border-bottom:1px solid #ddd">
 
    <tr>
 
        <td height="20px" style="height:20px" colspan="3"></td>
 
    </tr>
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
 
            <span style="font-weight:600;color:#395fa0">Message</span>
 
        </td>
 
        <td width="30px" style="width:30px"></td>
 
    </tr>
 
    <tr>
 
        <td height="20px" style="height:20px" colspan="3"></td>
 
    </tr>
 
</table>
 
                    </td>
 
                </tr>
 
                <tr>
 
                    <td height="30px" style="height:30px" colspan="3"></td>
 
                </tr>
 
                <tr>
 
                    <td></td>
 
                    <td>
 
<table cellpadding="0" cellspacing="0" border="0" width="100%">
 
    <tr>
 
        <td style="font-family:Lucida Console,Consolas,Monaco,Inconsolata,Liberation Mono,monospace;white-space:pre-wrap"><div class="formatted-fixed">This is the &#39;body&#39; of the &quot;test&quot; message<br/> - nothing interesting here except indentation.</div></td>
 
    </tr>
 
</table>
 
                    </td>
 
                    <td></td>
 
                </tr>
 
                <tr>
 
                    <td height="30px" style="height:30px" colspan="3"></td>
 
                </tr>
 
            </table>
 
        </td>
 
        <td width="30px" style="width:30px"></td>
 
    </tr>
 
</table>
 
<!--/body-->
 
<!--/html-->
 
<hr/>
 
<hr/>
 
<h1>registration</h1>
 
<pre>
 
From: u1
 
From: u1 u1 <name@example.com>
 
To: u2@example.com
 
Subject: New user newbie registered
 
</pre>
 
<hr/>
 
<pre>http://newbie.org
 

	
 
New User Registration
 

	
 

	
 
Username: newbie
 

	
 
Full Name: New Full Name
 

	
 
Email: new@email.com
 

	
 

	
 
View User Profile: http://newbie.org
 
</pre>
 
<hr/>
 
<!--!doctype html-->
 
<!--html lang="en"-->
 
<!--head-->
 
    <!--title--><!--/title-->
 
    <!--meta name="viewport" content="width=device-width"-->
 
    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
 
<!--/head-->
 
<!--body-->
 
<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td>
 
            <table width="100%" cellpadding="0" cellspacing="0" border="0"
 
                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
 
                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
 
                <tr>
 
                    <td colspan="3">
 
<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
 
       style="border-bottom:1px solid #ddd">
 
    <tr>
 
        <td height="20px" style="height:20px" colspan="3"></td>
 
    </tr>
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
 
            <a style="text-decoration:none;font-weight:600;color:#395fa0" href="http://newbie.org"
 
               target="_blank">New User Registration</a>
 
        </td>
 
        <td width="30px" style="width:30px"></td>
 
@@ -836,97 +836,97 @@ View User Profile: http://newbie.org
 
    <tr>
 
        <td>
 
            Email:
 
        </td>
 
        <td style="border:#ddd 1px solid;background:#f9f9f9">
 
            new@email.com
 
        </td>
 
    </tr>
 
    <tr>
 
        <td colspan="2">
 
<center>
 
    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
 
        <tr>
 
            <td height="25px" style="height:25px"></td>
 
        </tr>
 
        <tr>
 
            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
 
                <a href="http://newbie.org" style="text-decoration:none;display:block" target="_blank">
 
                    <center>
 
                        <font size="3">
 
                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View User Profile</span>
 
                        </font>
 
                    </center>
 
                </a>
 
            </td>
 
        </tr>
 
    </table>
 
</center>
 
        </td>
 
    </tr>
 
</table>
 
                    </td>
 
                    <td></td>
 
                </tr>
 
                <tr>
 
                    <td height="30px" style="height:30px" colspan="3"></td>
 
                </tr>
 
            </table>
 
        </td>
 
        <td width="30px" style="width:30px"></td>
 
    </tr>
 
</table>
 
<!--/body-->
 
<!--/html-->
 
<hr/>
 
<hr/>
 
<h1>pull_request, is_mention=False</h1>
 
<pre>
 
From: u1
 
From: u1 u1 <name@example.com>
 
To: u2@example.com
 
Subject: [Review] repo/name PR #7 "The Title" from devbranch by u2
 
</pre>
 
<hr/>
 
<pre>http://pr.org/7
 

	
 
Added as Reviewer of Pull Request #7 "The Title" by Requesting User (root)
 

	
 

	
 
Pull request #7 "The Title" by u2 u3 (u2)
 
from https://dev.org/repo branch devbranch
 
to http://mainline.com/repo branch trunk
 

	
 

	
 
Description:
 

	
 
This PR is 'awesome' because it does <stuff>
 
 - please approve indented!
 

	
 

	
 
Changesets:
 

	
 
Introduce one and two
 
Make one plus two equal tree
 

	
 

	
 
View Pull Request: http://pr.org/7
 
</pre>
 
<hr/>
 
<!--!doctype html-->
 
<!--html lang="en"-->
 
<!--head-->
 
    <!--title--><!--/title-->
 
    <!--meta name="viewport" content="width=device-width"-->
 
    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
 
<!--/head-->
 
<!--body-->
 
<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td>
 
            <table width="100%" cellpadding="0" cellspacing="0" border="0"
 
                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
 
                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
 
                <tr>
 
                    <td colspan="3">
 
<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
 
       style="border-bottom:1px solid #ddd">
 
@@ -1027,97 +1027,97 @@ View Pull Request: http://pr.org/7
 
                        <a style="color:#395fa0;text-decoration:none"
 
                           href="http://changeset_home/?repo_name=repo_org&amp;revision=567fed567fed567fed567fed567fed567fed567fed">
 
                            Make one plus two equal tree
 
                        </a>
 
                    </li>
 
            </ul>
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
<center>
 
    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
 
        <tr>
 
            <td height="25px" style="height:25px"></td>
 
        </tr>
 
        <tr>
 
            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
 
                <a href="http://pr.org/7" style="text-decoration:none;display:block" target="_blank">
 
                    <center>
 
                        <font size="3">
 
                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Pull Request</span>
 
                        </font>
 
                    </center>
 
                </a>
 
            </td>
 
        </tr>
 
    </table>
 
</center>
 
        </td>
 
    </tr>
 
</table>
 
                    </td>
 
                    <td></td>
 
                </tr>
 
                <tr>
 
                    <td height="30px" style="height:30px" colspan="3"></td>
 
                </tr>
 
            </table>
 
        </td>
 
        <td width="30px" style="width:30px"></td>
 
    </tr>
 
</table>
 
<!--/body-->
 
<!--/html-->
 
<hr/>
 
<hr/>
 
<h1>pull_request, is_mention=True</h1>
 
<pre>
 
From: u1
 
From: u1 u1 <name@example.com>
 
To: u2@example.com
 
Subject: [Review] repo/name PR #7 "The Title" from devbranch by u2
 
</pre>
 
<hr/>
 
<pre>http://pr.org/7
 

	
 
Mention on Pull Request #7 "The Title" by Requesting User (root)
 

	
 

	
 
Pull request #7 "The Title" by u2 u3 (u2)
 
from https://dev.org/repo branch devbranch
 
to http://mainline.com/repo branch trunk
 

	
 

	
 
Description:
 

	
 
This PR is 'awesome' because it does <stuff>
 
 - please approve indented!
 

	
 

	
 
Changesets:
 

	
 
Introduce one and two
 
Make one plus two equal tree
 

	
 

	
 
View Pull Request: http://pr.org/7
 
</pre>
 
<hr/>
 
<!--!doctype html-->
 
<!--html lang="en"-->
 
<!--head-->
 
    <!--title--><!--/title-->
 
    <!--meta name="viewport" content="width=device-width"-->
 
    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
 
<!--/head-->
 
<!--body-->
 
<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td>
 
            <table width="100%" cellpadding="0" cellspacing="0" border="0"
 
                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
 
                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
 
                <tr>
 
                    <td colspan="3">
 
<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
 
       style="border-bottom:1px solid #ddd">
 
@@ -1218,97 +1218,97 @@ View Pull Request: http://pr.org/7
 
                        <a style="color:#395fa0;text-decoration:none"
 
                           href="http://changeset_home/?repo_name=repo_org&amp;revision=567fed567fed567fed567fed567fed567fed567fed">
 
                            Make one plus two equal tree
 
                        </a>
 
                    </li>
 
            </ul>
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
<center>
 
    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
 
        <tr>
 
            <td height="25px" style="height:25px"></td>
 
        </tr>
 
        <tr>
 
            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
 
                <a href="http://pr.org/7" style="text-decoration:none;display:block" target="_blank">
 
                    <center>
 
                        <font size="3">
 
                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Pull Request</span>
 
                        </font>
 
                    </center>
 
                </a>
 
            </td>
 
        </tr>
 
    </table>
 
</center>
 
        </td>
 
    </tr>
 
</table>
 
                    </td>
 
                    <td></td>
 
                </tr>
 
                <tr>
 
                    <td height="30px" style="height:30px" colspan="3"></td>
 
                </tr>
 
            </table>
 
        </td>
 
        <td width="30px" style="width:30px"></td>
 
    </tr>
 
</table>
 
<!--/body-->
 
<!--/html-->
 
<hr/>
 
<hr/>
 
<h1>pull_request_comment, is_mention=False, status_change=None, closing_pr=False</h1>
 
<pre>
 
From: u1
 
From: u1 u1 <name@example.com>
 
To: u2@example.com
 
Subject: [Comment] repo/name PR #7 "The Title" from devbranch by u2
 
</pre>
 
<hr/>
 
<pre>http://pr.org/comment
 

	
 
Comment on Pull Request #7 "The Title"
 

	
 

	
 
Opinionated User (jsmith):
 

	
 
Me too!
 

	
 
 - and indented on second line
 

	
 

	
 
Pull request #7 "The Title" by u2 u3 (u2)
 
from https://dev.org/repo branch devbranch
 
to http://mainline.com/repo branch trunk
 

	
 

	
 
View Comment: http://pr.org/comment
 
</pre>
 
<hr/>
 
<!--!doctype html-->
 
<!--html lang="en"-->
 
<!--head-->
 
    <!--title--><!--/title-->
 
    <!--meta name="viewport" content="width=device-width"-->
 
    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
 
<!--/head-->
 
<!--body-->
 
<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td>
 
            <table width="100%" cellpadding="0" cellspacing="0" border="0"
 
                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
 
                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
 
                <tr>
 
                    <td colspan="3">
 
<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
 
       style="border-bottom:1px solid #ddd">
 
    <tr>
 
        <td height="20px" style="height:20px" colspan="3"></td>
 
    </tr>
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
@@ -1385,97 +1385,97 @@ View Comment: http://pr.org/comment
 
                to
 
                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
 
                   href="http://mainline.com/repo">http://mainline.com/repo</a>
 
                branch
 
                <span style="border:#ddd 1px solid;background:#f9f9f9">trunk</span>:
 
            </div>
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
<center>
 
    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
 
        <tr>
 
            <td height="25px" style="height:25px"></td>
 
        </tr>
 
        <tr>
 
            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
 
                <a href="http://pr.org/comment" style="text-decoration:none;display:block" target="_blank">
 
                    <center>
 
                        <font size="3">
 
                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
 
                        </font>
 
                    </center>
 
                </a>
 
            </td>
 
        </tr>
 
    </table>
 
</center>
 
        </td>
 
    </tr>
 
</table>
 
                    </td>
 
                    <td></td>
 
                </tr>
 
                <tr>
 
                    <td height="30px" style="height:30px" colspan="3"></td>
 
                </tr>
 
            </table>
 
        </td>
 
        <td width="30px" style="width:30px"></td>
 
    </tr>
 
</table>
 
<!--/body-->
 
<!--/html-->
 
<hr/>
 
<hr/>
 
<h1>pull_request_comment, is_mention=True, status_change=None, closing_pr=False</h1>
 
<pre>
 
From: u1
 
From: u1 u1 <name@example.com>
 
To: u2@example.com
 
Subject: [Comment] repo/name PR #7 "The Title" from devbranch by u2
 
</pre>
 
<hr/>
 
<pre>http://pr.org/comment
 

	
 
Mention in Comment on Pull Request #7 "The Title"
 

	
 

	
 
Opinionated User (jsmith):
 

	
 
Me too!
 

	
 
 - and indented on second line
 

	
 

	
 
Pull request #7 "The Title" by u2 u3 (u2)
 
from https://dev.org/repo branch devbranch
 
to http://mainline.com/repo branch trunk
 

	
 

	
 
View Comment: http://pr.org/comment
 
</pre>
 
<hr/>
 
<!--!doctype html-->
 
<!--html lang="en"-->
 
<!--head-->
 
    <!--title--><!--/title-->
 
    <!--meta name="viewport" content="width=device-width"-->
 
    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
 
<!--/head-->
 
<!--body-->
 
<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td>
 
            <table width="100%" cellpadding="0" cellspacing="0" border="0"
 
                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
 
                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
 
                <tr>
 
                    <td colspan="3">
 
<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
 
       style="border-bottom:1px solid #ddd">
 
    <tr>
 
        <td height="20px" style="height:20px" colspan="3"></td>
 
    </tr>
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
@@ -1552,97 +1552,97 @@ View Comment: http://pr.org/comment
 
                to
 
                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
 
                   href="http://mainline.com/repo">http://mainline.com/repo</a>
 
                branch
 
                <span style="border:#ddd 1px solid;background:#f9f9f9">trunk</span>:
 
            </div>
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
<center>
 
    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
 
        <tr>
 
            <td height="25px" style="height:25px"></td>
 
        </tr>
 
        <tr>
 
            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
 
                <a href="http://pr.org/comment" style="text-decoration:none;display:block" target="_blank">
 
                    <center>
 
                        <font size="3">
 
                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
 
                        </font>
 
                    </center>
 
                </a>
 
            </td>
 
        </tr>
 
    </table>
 
</center>
 
        </td>
 
    </tr>
 
</table>
 
                    </td>
 
                    <td></td>
 
                </tr>
 
                <tr>
 
                    <td height="30px" style="height:30px" colspan="3"></td>
 
                </tr>
 
            </table>
 
        </td>
 
        <td width="30px" style="width:30px"></td>
 
    </tr>
 
</table>
 
<!--/body-->
 
<!--/html-->
 
<hr/>
 
<hr/>
 
<h1>pull_request_comment, is_mention=False, status_change='Under Review', closing_pr=False</h1>
 
<pre>
 
From: u1
 
From: u1 u1 <name@example.com>
 
To: u2@example.com
 
Subject: [Under Review: Comment] repo/name PR #7 "The Title" from devbranch by u2
 
</pre>
 
<hr/>
 
<pre>http://pr.org/comment
 

	
 
Comment on Pull Request #7 "The Title"
 

	
 

	
 
Opinionated User (jsmith):
 

	
 
Status change: Under Review
 

	
 
Me too!
 

	
 
 - and indented on second line
 

	
 

	
 
Pull request #7 "The Title" by u2 u3 (u2)
 
from https://dev.org/repo branch devbranch
 
to http://mainline.com/repo branch trunk
 

	
 

	
 
View Comment: http://pr.org/comment
 
</pre>
 
<hr/>
 
<!--!doctype html-->
 
<!--html lang="en"-->
 
<!--head-->
 
    <!--title--><!--/title-->
 
    <!--meta name="viewport" content="width=device-width"-->
 
    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
 
<!--/head-->
 
<!--body-->
 
<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td>
 
            <table width="100%" cellpadding="0" cellspacing="0" border="0"
 
                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
 
                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
 
                <tr>
 
                    <td colspan="3">
 
<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
 
       style="border-bottom:1px solid #ddd">
 
    <tr>
 
        <td height="20px" style="height:20px" colspan="3"></td>
 
    </tr>
 
@@ -1737,97 +1737,97 @@ View Comment: http://pr.org/comment
 
                to
 
                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
 
                   href="http://mainline.com/repo">http://mainline.com/repo</a>
 
                branch
 
                <span style="border:#ddd 1px solid;background:#f9f9f9">trunk</span>:
 
            </div>
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
<center>
 
    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
 
        <tr>
 
            <td height="25px" style="height:25px"></td>
 
        </tr>
 
        <tr>
 
            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
 
                <a href="http://pr.org/comment" style="text-decoration:none;display:block" target="_blank">
 
                    <center>
 
                        <font size="3">
 
                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
 
                        </font>
 
                    </center>
 
                </a>
 
            </td>
 
        </tr>
 
    </table>
 
</center>
 
        </td>
 
    </tr>
 
</table>
 
                    </td>
 
                    <td></td>
 
                </tr>
 
                <tr>
 
                    <td height="30px" style="height:30px" colspan="3"></td>
 
                </tr>
 
            </table>
 
        </td>
 
        <td width="30px" style="width:30px"></td>
 
    </tr>
 
</table>
 
<!--/body-->
 
<!--/html-->
 
<hr/>
 
<hr/>
 
<h1>pull_request_comment, is_mention=True, status_change='Under Review', closing_pr=False</h1>
 
<pre>
 
From: u1
 
From: u1 u1 <name@example.com>
 
To: u2@example.com
 
Subject: [Under Review: Comment] repo/name PR #7 "The Title" from devbranch by u2
 
</pre>
 
<hr/>
 
<pre>http://pr.org/comment
 

	
 
Mention in Comment on Pull Request #7 "The Title"
 

	
 

	
 
Opinionated User (jsmith):
 

	
 
Status change: Under Review
 

	
 
Me too!
 

	
 
 - and indented on second line
 

	
 

	
 
Pull request #7 "The Title" by u2 u3 (u2)
 
from https://dev.org/repo branch devbranch
 
to http://mainline.com/repo branch trunk
 

	
 

	
 
View Comment: http://pr.org/comment
 
</pre>
 
<hr/>
 
<!--!doctype html-->
 
<!--html lang="en"-->
 
<!--head-->
 
    <!--title--><!--/title-->
 
    <!--meta name="viewport" content="width=device-width"-->
 
    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
 
<!--/head-->
 
<!--body-->
 
<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td>
 
            <table width="100%" cellpadding="0" cellspacing="0" border="0"
 
                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
 
                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
 
                <tr>
 
                    <td colspan="3">
 
<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
 
       style="border-bottom:1px solid #ddd">
 
    <tr>
 
        <td height="20px" style="height:20px" colspan="3"></td>
 
    </tr>
 
@@ -1922,97 +1922,97 @@ View Comment: http://pr.org/comment
 
                to
 
                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
 
                   href="http://mainline.com/repo">http://mainline.com/repo</a>
 
                branch
 
                <span style="border:#ddd 1px solid;background:#f9f9f9">trunk</span>:
 
            </div>
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
<center>
 
    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
 
        <tr>
 
            <td height="25px" style="height:25px"></td>
 
        </tr>
 
        <tr>
 
            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
 
                <a href="http://pr.org/comment" style="text-decoration:none;display:block" target="_blank">
 
                    <center>
 
                        <font size="3">
 
                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
 
                        </font>
 
                    </center>
 
                </a>
 
            </td>
 
        </tr>
 
    </table>
 
</center>
 
        </td>
 
    </tr>
 
</table>
 
                    </td>
 
                    <td></td>
 
                </tr>
 
                <tr>
 
                    <td height="30px" style="height:30px" colspan="3"></td>
 
                </tr>
 
            </table>
 
        </td>
 
        <td width="30px" style="width:30px"></td>
 
    </tr>
 
</table>
 
<!--/body-->
 
<!--/html-->
 
<hr/>
 
<hr/>
 
<h1>pull_request_comment, is_mention=False, status_change=None, closing_pr=True</h1>
 
<pre>
 
From: u1
 
From: u1 u1 <name@example.com>
 
To: u2@example.com
 
Subject: [Closing: Comment] repo/name PR #7 "The Title" from devbranch by u2
 
</pre>
 
<hr/>
 
<pre>http://pr.org/comment
 

	
 
Pull Request #7 "The Title" Closed
 

	
 

	
 
Opinionated User (jsmith):
 

	
 
The pull request has been closed.
 

	
 
Me too!
 

	
 
 - and indented on second line
 

	
 

	
 
Pull request #7 "The Title" by u2 u3 (u2)
 
from https://dev.org/repo branch devbranch
 
to http://mainline.com/repo branch trunk
 

	
 

	
 
View Comment: http://pr.org/comment
 
</pre>
 
<hr/>
 
<!--!doctype html-->
 
<!--html lang="en"-->
 
<!--head-->
 
    <!--title--><!--/title-->
 
    <!--meta name="viewport" content="width=device-width"-->
 
    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
 
<!--/head-->
 
<!--body-->
 
<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td>
 
            <table width="100%" cellpadding="0" cellspacing="0" border="0"
 
                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
 
                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
 
                <tr>
 
                    <td colspan="3">
 
<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
 
       style="border-bottom:1px solid #ddd">
 
    <tr>
 
        <td height="20px" style="height:20px" colspan="3"></td>
 
    </tr>
 
@@ -2106,97 +2106,97 @@ View Comment: http://pr.org/comment
 
                to
 
                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
 
                   href="http://mainline.com/repo">http://mainline.com/repo</a>
 
                branch
 
                <span style="border:#ddd 1px solid;background:#f9f9f9">trunk</span>:
 
            </div>
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
<center>
 
    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
 
        <tr>
 
            <td height="25px" style="height:25px"></td>
 
        </tr>
 
        <tr>
 
            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
 
                <a href="http://pr.org/comment" style="text-decoration:none;display:block" target="_blank">
 
                    <center>
 
                        <font size="3">
 
                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
 
                        </font>
 
                    </center>
 
                </a>
 
            </td>
 
        </tr>
 
    </table>
 
</center>
 
        </td>
 
    </tr>
 
</table>
 
                    </td>
 
                    <td></td>
 
                </tr>
 
                <tr>
 
                    <td height="30px" style="height:30px" colspan="3"></td>
 
                </tr>
 
            </table>
 
        </td>
 
        <td width="30px" style="width:30px"></td>
 
    </tr>
 
</table>
 
<!--/body-->
 
<!--/html-->
 
<hr/>
 
<hr/>
 
<h1>pull_request_comment, is_mention=True, status_change=None, closing_pr=True</h1>
 
<pre>
 
From: u1
 
From: u1 u1 <name@example.com>
 
To: u2@example.com
 
Subject: [Closing: Comment] repo/name PR #7 "The Title" from devbranch by u2
 
</pre>
 
<hr/>
 
<pre>http://pr.org/comment
 

	
 
Mention in Comment on Pull Request #7 "The Title"
 

	
 

	
 
Opinionated User (jsmith):
 

	
 
The pull request has been closed.
 

	
 
Me too!
 

	
 
 - and indented on second line
 

	
 

	
 
Pull request #7 "The Title" by u2 u3 (u2)
 
from https://dev.org/repo branch devbranch
 
to http://mainline.com/repo branch trunk
 

	
 

	
 
View Comment: http://pr.org/comment
 
</pre>
 
<hr/>
 
<!--!doctype html-->
 
<!--html lang="en"-->
 
<!--head-->
 
    <!--title--><!--/title-->
 
    <!--meta name="viewport" content="width=device-width"-->
 
    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
 
<!--/head-->
 
<!--body-->
 
<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td>
 
            <table width="100%" cellpadding="0" cellspacing="0" border="0"
 
                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
 
                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
 
                <tr>
 
                    <td colspan="3">
 
<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
 
       style="border-bottom:1px solid #ddd">
 
    <tr>
 
        <td height="20px" style="height:20px" colspan="3"></td>
 
    </tr>
 
@@ -2290,97 +2290,97 @@ View Comment: http://pr.org/comment
 
                to
 
                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
 
                   href="http://mainline.com/repo">http://mainline.com/repo</a>
 
                branch
 
                <span style="border:#ddd 1px solid;background:#f9f9f9">trunk</span>:
 
            </div>
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
<center>
 
    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
 
        <tr>
 
            <td height="25px" style="height:25px"></td>
 
        </tr>
 
        <tr>
 
            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
 
                <a href="http://pr.org/comment" style="text-decoration:none;display:block" target="_blank">
 
                    <center>
 
                        <font size="3">
 
                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
 
                        </font>
 
                    </center>
 
                </a>
 
            </td>
 
        </tr>
 
    </table>
 
</center>
 
        </td>
 
    </tr>
 
</table>
 
                    </td>
 
                    <td></td>
 
                </tr>
 
                <tr>
 
                    <td height="30px" style="height:30px" colspan="3"></td>
 
                </tr>
 
            </table>
 
        </td>
 
        <td width="30px" style="width:30px"></td>
 
    </tr>
 
</table>
 
<!--/body-->
 
<!--/html-->
 
<hr/>
 
<hr/>
 
<h1>pull_request_comment, is_mention=False, status_change='Under Review', closing_pr=True</h1>
 
<pre>
 
From: u1
 
From: u1 u1 <name@example.com>
 
To: u2@example.com
 
Subject: [Under Review, Closing: Comment] repo/name PR #7 "The Title" from devbranch by u2
 
</pre>
 
<hr/>
 
<pre>http://pr.org/comment
 

	
 
Pull Request #7 "The Title" Closed
 

	
 

	
 
Opinionated User (jsmith):
 

	
 
Status change: Under Review
 

	
 
The pull request has been closed.
 

	
 
Me too!
 

	
 
 - and indented on second line
 

	
 

	
 
Pull request #7 "The Title" by u2 u3 (u2)
 
from https://dev.org/repo branch devbranch
 
to http://mainline.com/repo branch trunk
 

	
 

	
 
View Comment: http://pr.org/comment
 
</pre>
 
<hr/>
 
<!--!doctype html-->
 
<!--html lang="en"-->
 
<!--head-->
 
    <!--title--><!--/title-->
 
    <!--meta name="viewport" content="width=device-width"-->
 
    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
 
<!--/head-->
 
<!--body-->
 
<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td>
 
            <table width="100%" cellpadding="0" cellspacing="0" border="0"
 
                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
 
                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
 
                <tr>
 
                    <td colspan="3">
 
<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
 
       style="border-bottom:1px solid #ddd">
 
    <tr>
 
@@ -2480,97 +2480,97 @@ View Comment: http://pr.org/comment
 
                to
 
                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
 
                   href="http://mainline.com/repo">http://mainline.com/repo</a>
 
                branch
 
                <span style="border:#ddd 1px solid;background:#f9f9f9">trunk</span>:
 
            </div>
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
<center>
 
    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
 
        <tr>
 
            <td height="25px" style="height:25px"></td>
 
        </tr>
 
        <tr>
 
            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
 
                <a href="http://pr.org/comment" style="text-decoration:none;display:block" target="_blank">
 
                    <center>
 
                        <font size="3">
 
                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
 
                        </font>
 
                    </center>
 
                </a>
 
            </td>
 
        </tr>
 
    </table>
 
</center>
 
        </td>
 
    </tr>
 
</table>
 
                    </td>
 
                    <td></td>
 
                </tr>
 
                <tr>
 
                    <td height="30px" style="height:30px" colspan="3"></td>
 
                </tr>
 
            </table>
 
        </td>
 
        <td width="30px" style="width:30px"></td>
 
    </tr>
 
</table>
 
<!--/body-->
 
<!--/html-->
 
<hr/>
 
<hr/>
 
<h1>pull_request_comment, is_mention=True, status_change='Under Review', closing_pr=True</h1>
 
<pre>
 
From: u1
 
From: u1 u1 <name@example.com>
 
To: u2@example.com
 
Subject: [Under Review, Closing: Comment] repo/name PR #7 "The Title" from devbranch by u2
 
</pre>
 
<hr/>
 
<pre>http://pr.org/comment
 

	
 
Mention in Comment on Pull Request #7 "The Title"
 

	
 

	
 
Opinionated User (jsmith):
 

	
 
Status change: Under Review
 

	
 
The pull request has been closed.
 

	
 
Me too!
 

	
 
 - and indented on second line
 

	
 

	
 
Pull request #7 "The Title" by u2 u3 (u2)
 
from https://dev.org/repo branch devbranch
 
to http://mainline.com/repo branch trunk
 

	
 

	
 
View Comment: http://pr.org/comment
 
</pre>
 
<hr/>
 
<!--!doctype html-->
 
<!--html lang="en"-->
 
<!--head-->
 
    <!--title--><!--/title-->
 
    <!--meta name="viewport" content="width=device-width"-->
 
    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
 
<!--/head-->
 
<!--body-->
 
<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td>
 
            <table width="100%" cellpadding="0" cellspacing="0" border="0"
 
                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
 
                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
 
                <tr>
 
                    <td colspan="3">
 
<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
 
       style="border-bottom:1px solid #ddd">
 
    <tr>
 
@@ -2670,97 +2670,97 @@ View Comment: http://pr.org/comment
 
                to
 
                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
 
                   href="http://mainline.com/repo">http://mainline.com/repo</a>
 
                branch
 
                <span style="border:#ddd 1px solid;background:#f9f9f9">trunk</span>:
 
            </div>
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
<center>
 
    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
 
        <tr>
 
            <td height="25px" style="height:25px"></td>
 
        </tr>
 
        <tr>
 
            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
 
                <a href="http://pr.org/comment" style="text-decoration:none;display:block" target="_blank">
 
                    <center>
 
                        <font size="3">
 
                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
 
                        </font>
 
                    </center>
 
                </a>
 
            </td>
 
        </tr>
 
    </table>
 
</center>
 
        </td>
 
    </tr>
 
</table>
 
                    </td>
 
                    <td></td>
 
                </tr>
 
                <tr>
 
                    <td height="30px" style="height:30px" colspan="3"></td>
 
                </tr>
 
            </table>
 
        </td>
 
        <td width="30px" style="width:30px"></td>
 
    </tr>
 
</table>
 
<!--/body-->
 
<!--/html-->
 
<hr/>
 
<hr/>
 
<h1>TYPE_PASSWORD_RESET</h1>
 
<pre>
 
From: u1
 
From: u1 u1 <name@example.com>
 
To: john@doe.com
 
Subject: Password reset link
 
</pre>
 
<hr/>
 
<pre>Password Reset Request
 

	
 
Hello John Doe,
 

	
 
We have received a request to reset the password for your account.
 

	
 
To set a new password, click the following link:
 

	
 
http://reset.com/decbf64715098db5b0bd23eab44bd792670ab746
 

	
 
Should you not be able to use the link above, please type the following code into the password reset form:
 
decbf64715098db5b0bd23eab44bd792670ab746
 

	
 
If it weren't you who requested the password reset, just disregard this message.
 
</pre>
 
<hr/>
 
<!--!doctype html-->
 
<!--html lang="en"-->
 
<!--head-->
 
    <!--title--><!--/title-->
 
    <!--meta name="viewport" content="width=device-width"-->
 
    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
 
<!--/head-->
 
<!--body-->
 
<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td>
 
            <table width="100%" cellpadding="0" cellspacing="0" border="0"
 
                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
 
                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
 
                <tr>
 
                    <td colspan="3">
 
<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
 
       style="border-bottom:1px solid #ddd">
 
    <tr>
 
        <td height="20px" style="height:20px" colspan="3"></td>
 
    </tr>
 
    <tr>
 
        <td width="30px" style="width:30px"></td>
 
        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
 
            <span style="font-weight:600;color:#395fa0">Password Reset Request</span>
 
        </td>
 
        <td width="30px" style="width:30px"></td>
kallithea/tests/models/test_notifications.py
Show inline comments
 
import os
 
import re
 

	
 
import mock
 
from tg.util.webtest import test_context
 

	
 
import kallithea.lib.celerylib
 
import kallithea.lib.celerylib.tasks
 
from kallithea.lib import helpers as h
 
from kallithea.model.db import User
 
from kallithea.model.meta import Session
 
from kallithea.model.notification import EmailNotificationModel, NotificationModel
 
from kallithea.model.user import UserModel
 
from kallithea.tests import base
 

	
 

	
 
class TestNotifications(base.TestController):
 

	
 
    def setup_method(self, method):
 
        Session.remove()
 
        u1 = UserModel().create_or_update(username='u1',
 
                                        password='qweqwe',
 
                                        email='u1@example.com',
 
                                        firstname='u1', lastname='u1')
 
        Session().commit()
 
        self.u1 = u1.user_id
 

	
 
        u2 = UserModel().create_or_update(username='u2',
 
                                        password='qweqwe',
 
                                        email='u2@example.com',
 
                                        firstname='u2', lastname='u3')
 
        Session().commit()
 
        self.u2 = u2.user_id
 

	
 
        u3 = UserModel().create_or_update(username='u3',
 
                                        password='qweqwe',
 
                                        email='u3@example.com',
 
                                        firstname='u3', lastname='u3')
 
        Session().commit()
 
        self.u3 = u3.user_id
 

	
 
    def test_create_notification(self):
 
        with test_context(self.app):
 
            usrs = [self.u1, self.u2]
 

	
 
            def send_email(recipients, subject, body='', html_body='', headers=None, author=None):
 
            def send_email(recipients, subject, body='', html_body='', headers=None, from_name=None):
 
                assert recipients == ['u2@example.com']
 
                assert subject == 'Test Message'
 
                assert body == "hi there"
 
                assert '>hi there<' in html_body
 
                assert author.username == 'u1'
 
                assert from_name == 'u1 u1'
 
            with mock.patch.object(kallithea.lib.celerylib.tasks, 'send_email', send_email):
 
                NotificationModel().create(created_by=self.u1,
 
                                                   subject='subj', body='hi there',
 
                                                   recipients=usrs)
 

	
 
    @mock.patch.object(h, 'canonical_url', (lambda arg, **kwargs: 'http://%s/?%s' % (arg, '&'.join('%s=%s' % (k, v) for (k, v) in sorted(kwargs.items())))))
 
    def test_dump_html_mails(self):
 
        # Exercise all notification types and dump them to one big html file
 
        l = []
 

	
 
        def send_email(recipients, subject, body='', html_body='', headers=None, author=None):
 
        def send_email(recipients, subject, body='', html_body='', headers=None, from_name=None):
 
            l.append('<hr/>\n')
 
            l.append('<h1>%s</h1>\n' % desc) # desc is from outer scope
 
            l.append('<pre>\n')
 
            l.append('From: %s\n' % author.username)
 
            l.append('From: %s <name@example.com>\n' % from_name)
 
            l.append('To: %s\n' % ' '.join(recipients))
 
            l.append('Subject: %s\n' % subject)
 
            l.append('</pre>\n')
 
            l.append('<hr/>\n')
 
            l.append('<pre>%s</pre>\n' % body)
 
            l.append('<hr/>\n')
 
            l.append(html_body)
 
            l.append('<hr/>\n')
 

	
 
        with test_context(self.app):
 
            with mock.patch.object(kallithea.lib.celerylib.tasks, 'send_email', send_email):
 
                pr_kwargs = dict(
 
                    pr_nice_id='#7',
 
                    pr_title='The Title',
 
                    pr_title_short='The Title',
 
                    pr_url='http://pr.org/7',
 
                    pr_target_repo='http://mainline.com/repo',
 
                    pr_target_branch='trunk',
 
                    pr_source_repo='https://dev.org/repo',
 
                    pr_source_branch='devbranch',
 
                    pr_owner=User.get(self.u2),
 
                    pr_owner_username='u2'
 
                    )
 

	
 
                for type_, body, kwargs in [
 
                        (NotificationModel.TYPE_CHANGESET_COMMENT,
 
                         'This is the new \'comment\'.\n\n - and here it ends indented.',
 
                         dict(
 
                            short_id='cafe1234',
 
                            raw_id='cafe1234c0ffeecafe',
 
                            branch='brunch',
 
                            cs_comment_user='Opinionated User (jsmith)',
 
                            cs_comment_url='http://comment.org',
 
                            is_mention=[False, True],
 
                            message='This changeset did something clever which is hard to explain',
 
                            message_short='This changeset did something cl...',
 
                            status_change=[None, 'Approved'],
 
                            cs_target_repo='http://example.com/repo_target',
 
                            cs_url='http://changeset.com',
 
                            cs_author=User.get(self.u2))),
 
                        (NotificationModel.TYPE_MESSAGE,
 
                         'This is the \'body\' of the "test" message\n - nothing interesting here except indentation.',
 
                         dict()),
 
                        #(NotificationModel.TYPE_MENTION, '$body', None), # not used
 
                        (NotificationModel.TYPE_REGISTRATION,
 
                         'Registration body',
 
                         dict(
 
                            new_username='newbie',
 
                            registered_user_url='http://newbie.org',
 
                            new_email='new@email.com',
 
                            new_full_name='New Full Name')),
 
                        (NotificationModel.TYPE_PULL_REQUEST,
 
                         'This PR is \'awesome\' because it does <stuff>\n - please approve indented!',
 
                         dict(
 
                            pr_user_created='Requesting User (root)', # pr_owner should perhaps be used for @mention in description ...
 
                            is_mention=[False, True],
 
                            pr_revisions=[('123abc'*7, "Introduce one and two\n\nand that's it"), ('567fed'*7, 'Make one plus two equal tree')],
 
                            org_repo_name='repo_org',
 
                            **pr_kwargs)),
 
                        (NotificationModel.TYPE_PULL_REQUEST_COMMENT,
 
                         'Me too!\n\n - and indented on second line',
 
                         dict(
 
                            closing_pr=[False, True],
 
                            is_mention=[False, True],
 
                            pr_comment_user='Opinionated User (jsmith)',
 
                            pr_comment_url='http://pr.org/comment',
 
                            status_change=[None, 'Under Review'],
 
                            **pr_kwargs)),
 
                        ]:
 
                    kwargs['repo_name'] = 'repo/name'
 
                    params = [(type_, type_, body, kwargs)]
 
                    for param_name in ['is_mention', 'status_change', 'closing_pr']: # TODO: inline/general
 
                        if not isinstance(kwargs.get(param_name), list):
 
                            continue
 
                        new_params = []
 
                        for v in kwargs[param_name]:
 
                            for desc, type_, body, kwargs in params:
 
                                kwargs = dict(kwargs)
 
                                kwargs[param_name] = v
 
                                new_params.append(('%s, %s=%r' % (desc, param_name, v), type_, body, kwargs))
 
                        params = new_params
 

	
 
                    for desc, type_, body, kwargs in params:
 
                        # desc is used as "global" variable
 
                        NotificationModel().create(created_by=self.u1,
 
                                                           subject='unused', body=body, email_kwargs=kwargs,
 
                                                           recipients=[self.u2], type_=type_)
 

	
 
                # Email type TYPE_PASSWORD_RESET has no corresponding notification type - test it directly:
 
                desc = 'TYPE_PASSWORD_RESET'
 
                kwargs = dict(user='John Doe', reset_token='decbf64715098db5b0bd23eab44bd792670ab746', reset_url='http://reset.com/decbf64715098db5b0bd23eab44bd792670ab746')
 
                kallithea.lib.celerylib.tasks.send_email(['john@doe.com'],
 
                    "Password reset link",
 
                    EmailNotificationModel().get_email_tmpl(EmailNotificationModel.TYPE_PASSWORD_RESET, 'txt', **kwargs),
 
                    EmailNotificationModel().get_email_tmpl(EmailNotificationModel.TYPE_PASSWORD_RESET, 'html', **kwargs),
 
                    author=User.get(self.u1))
 
                    from_name=User.get(self.u1).full_name_or_username)
 

	
 
        out = '<!doctype html>\n<html lang="en">\n<head><title>Notifications</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head>\n<body>\n%s\n</body>\n</html>\n' % \
 
            re.sub(r'<(/?(?:!doctype|html|head|title|meta|body)\b[^>]*)>', r'<!--\1-->', ''.join(l))
 

	
 
        outfn = os.path.join(os.path.dirname(__file__), 'test_dump_html_mails.out.html')
 
        reffn = os.path.join(os.path.dirname(__file__), 'test_dump_html_mails.ref.html')
 
        with open(outfn, 'w') as f:
 
            f.write(out)
 
        with open(reffn) as f:
 
            ref = f.read()
 
        assert ref == out # copy test_dump_html_mails.out.html to test_dump_html_mails.ref.html to update expectations
 
        os.unlink(outfn)
kallithea/tests/other/test_mail.py
Show inline comments
 
@@ -90,108 +90,108 @@ class TestMail(base.TestController):
 
            'email_to': email_to,
 
        }
 
        with mock.patch('kallithea.lib.celerylib.tasks.config', config_mock):
 
            kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body)
 

	
 
        assert smtplib_mock.lastdest == set([base.TEST_USER_ADMIN_EMAIL] + email_to.split(','))
 
        assert smtplib_mock.lastsender == envelope_from
 
        assert 'From: %s' % envelope_from in smtplib_mock.lastmsg
 
        assert 'Subject: %s' % subject in smtplib_mock.lastmsg
 
        assert body in smtplib_mock.lastmsg
 
        assert html_body in smtplib_mock.lastmsg
 

	
 
    def test_send_mail_no_recipients_no_email_to(self):
 
        mailserver = 'smtp.mailserver.org'
 
        recipients = []
 
        envelope_from = 'noreply@mailserver.org'
 
        subject = 'subject'
 
        body = 'body'
 
        html_body = 'html_body'
 

	
 
        config_mock = {
 
            'smtp_server': mailserver,
 
            'app_email_from': envelope_from,
 
        }
 
        with mock.patch('kallithea.lib.celerylib.tasks.config', config_mock):
 
            kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body)
 

	
 
        assert smtplib_mock.lastdest == set([base.TEST_USER_ADMIN_EMAIL])
 
        assert smtplib_mock.lastsender == envelope_from
 
        assert 'From: %s' % envelope_from in smtplib_mock.lastmsg
 
        assert 'Subject: %s' % subject in smtplib_mock.lastmsg
 
        assert body in smtplib_mock.lastmsg
 
        assert html_body in smtplib_mock.lastmsg
 

	
 
    def test_send_mail_with_author(self):
 
        mailserver = 'smtp.mailserver.org'
 
        recipients = ['rcpt1', 'rcpt2']
 
        envelope_from = 'noreply@mailserver.org'
 
        subject = 'subject'
 
        body = 'body'
 
        html_body = 'html_body'
 
        author = User.get_by_username(base.TEST_USER_REGULAR_LOGIN)
 

	
 
        config_mock = {
 
            'smtp_server': mailserver,
 
            'app_email_from': envelope_from,
 
        }
 
        with mock.patch('kallithea.lib.celerylib.tasks.config', config_mock):
 
            kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body, author=author)
 
            kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body, from_name=author.full_name_or_username)
 

	
 
        assert smtplib_mock.lastdest == set(recipients)
 
        assert smtplib_mock.lastsender == envelope_from
 
        assert 'From: "Kallithea Admin (no-reply)" <%s>' % envelope_from in smtplib_mock.lastmsg
 
        assert 'Subject: %s' % subject in smtplib_mock.lastmsg
 
        assert body in smtplib_mock.lastmsg
 
        assert html_body in smtplib_mock.lastmsg
 

	
 
    def test_send_mail_with_author_full_mail_from(self):
 
        mailserver = 'smtp.mailserver.org'
 
        recipients = ['ræcpt1', 'receptor2 <rcpt2@example.com>', 'tæst@example.com', 'Tæst <test@example.com>']
 
        envelope_addr = 'noreply@mailserver.org'
 
        envelope_from = 'Söme Næme <%s>' % envelope_addr
 
        subject = 'subject'
 
        body = 'body'
 
        html_body = 'html_body'
 
        author = User.get_by_username(base.TEST_USER_REGULAR_LOGIN)
 

	
 
        config_mock = {
 
            'smtp_server': mailserver,
 
            'app_email_from': envelope_from,
 
        }
 
        with mock.patch('kallithea.lib.celerylib.tasks.config', config_mock):
 
            kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body, author=author)
 
            kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body, from_name=author.full_name_or_username)
 

	
 
        assert smtplib_mock.lastdest == set(recipients)
 
        assert smtplib_mock.lastsender == envelope_from
 
        assert 'From: "Kallithea Admin (no-reply)" <%s>' % envelope_addr in smtplib_mock.lastmsg
 
        assert 'Subject: %s' % subject in smtplib_mock.lastmsg
 
        assert body in smtplib_mock.lastmsg
 
        assert html_body in smtplib_mock.lastmsg
 

	
 
    def test_send_mail_extra_headers(self):
 
        mailserver = 'smtp.mailserver.org'
 
        recipients = ['rcpt1', 'rcpt2']
 
        envelope_from = 'noreply@mailserver.org'
 
        subject = 'subject'
 
        body = 'body'
 
        html_body = 'html_body'
 
        author = User(name='foo', lastname='(fubar) "baz"')
 
        headers = {'extra': 'yes'}
 

	
 
        config_mock = {
 
            'smtp_server': mailserver,
 
            'app_email_from': envelope_from,
 
        }
 
        with mock.patch('kallithea.lib.celerylib.tasks.config', config_mock):
 
            kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body,
 
                                                     author=author, headers=headers)
 
                                                     from_name=author.full_name_or_username, headers=headers)
 

	
 
        assert smtplib_mock.lastdest == set(recipients)
 
        assert smtplib_mock.lastsender == envelope_from
 
        assert r'From: "foo (fubar) \"baz\" (no-reply)" <%s>' % envelope_from in smtplib_mock.lastmsg
 
        assert 'Subject: %s' % subject in smtplib_mock.lastmsg
 
        assert body in smtplib_mock.lastmsg
 
        assert html_body in smtplib_mock.lastmsg
 
        assert 'Extra: yes' in smtplib_mock.lastmsg
 
        # verify that headers dict hasn't mutated by send_email
 
        assert headers == {'extra': 'yes'}
0 comments (0 inline, 0 general)