/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.171.46 by Martin Pool
Write outgoing message to a file to try to avoid deadlocks
1
# Copyright (C) 2005-2011 Canonical Ltd
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
6649.1.2 by Jelmer Vernooij
Fix remaining tests.
17
from __future__ import absolute_import
18
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
19
import subprocess
0.181.1 by Martin Pool
Change subprocess mail-sender to use a tempfile rather than a pipe
20
import tempfile
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
21
6649.1.1 by Jelmer Vernooij
Merge bzr-email plugin.
22
from ... import (
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
23
    errors,
24
    revision as _mod_revision,
25
    )
6649.1.1 by Jelmer Vernooij
Merge bzr-email plugin.
26
from ...config import (
0.171.55 by Jelmer Vernooij
Use config stacks.
27
    ListOption,
28
    Option,
29
    bool_from_store,
30
    int_from_store,
31
    )
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
32
6649.1.1 by Jelmer Vernooij
Merge bzr-email plugin.
33
from ...smtp_connection import SMTPConnection
34
from ...email_message import EmailMessage
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
35
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
36
37
class EmailSender(object):
38
    """An email message sender."""
39
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
40
    _smtplib_implementation = SMTPConnection
41
0.171.39 by Robert Collins
Draft support for mailing on push/pull.
42
    def __init__(self, branch, revision_id, config, local_branch=None,
7143.15.2 by Jelmer Vernooij
Run autopep8.
43
                 op='commit'):
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
44
        self.config = config
45
        self.branch = branch
0.171.24 by John Arbash Meinel
Switch to using a local repository if available,
46
        self.repository = branch.repository
47
        if (local_branch is not None and
7143.15.2 by Jelmer Vernooij
Run autopep8.
48
                local_branch.repository.has_revision(revision_id)):
0.171.24 by John Arbash Meinel
Switch to using a local repository if available,
49
            self.repository = local_branch.repository
50
        self._revision_id = revision_id
51
        self.revision = None
52
        self.revno = None
0.171.39 by Robert Collins
Draft support for mailing on push/pull.
53
        self.op = op
0.171.24 by John Arbash Meinel
Switch to using a local repository if available,
54
55
    def _setup_revision_and_revno(self):
56
        self.revision = self.repository.get_revision(self._revision_id)
57
        self.revno = self.branch.revision_id_to_revno(self._revision_id)
0.171.51 by Jelmer Vernooij
Avoid double lookup of revision, remove some unnecessary whitespace.
58
0.186.4 by Renato Silva
Small code fixes requested by Jelmer Vernooij for merging
59
    def _format(self, text):
0.185.1 by Renato Silva
New user options for email subject and body.
60
        fields = {
0.171.51 by Jelmer Vernooij
Avoid double lookup of revision, remove some unnecessary whitespace.
61
            'committer': self.revision.committer,
0.185.1 by Renato Silva
New user options for email subject and body.
62
            'message': self.revision.get_summary(),
0.171.51 by Jelmer Vernooij
Avoid double lookup of revision, remove some unnecessary whitespace.
63
            'revision': '%d' % self.revno,
64
            'url': self.url()
65
        }
0.185.1 by Renato Silva
New user options for email subject and body.
66
        for name, value in fields.items():
0.171.51 by Jelmer Vernooij
Avoid double lookup of revision, remove some unnecessary whitespace.
67
            text = text.replace('$%s' % name, value)
0.186.4 by Renato Silva
Small code fixes requested by Jelmer Vernooij for merging
68
        return text
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
69
70
    def body(self):
6649.1.1 by Jelmer Vernooij
Merge bzr-email plugin.
71
        from ... import log
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
72
73
        rev1 = rev2 = self.revno
74
        if rev1 == 0:
75
            rev1 = None
76
            rev2 = None
77
78
        # use 'replace' so that we don't abort if trying to write out
79
        # in e.g. the default C locale.
80
0.175.11 by John Arbash Meinel
Cleanup from review comments by Marius Gedminas
81
        # We must use StringIO.StringIO because we want a Unicode string that
82
        # we can pass to send_email and have that do the proper encoding.
6791.2.3 by Jelmer Vernooij
Fix more imports.
83
        from ...sixish import StringIO
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
84
        outf = StringIO()
0.175.13 by John Arbash Meinel
Add an entry in the body to make it easier to find the url
85
0.171.55 by Jelmer Vernooij
Use config stacks.
86
        _body = self.config.get('post_commit_body')
0.186.4 by Renato Silva
Small code fixes requested by Jelmer Vernooij for merging
87
        if _body is None:
0.171.51 by Jelmer Vernooij
Avoid double lookup of revision, remove some unnecessary whitespace.
88
            _body = 'At %s\n\n' % self.url()
0.186.4 by Renato Silva
Small code fixes requested by Jelmer Vernooij for merging
89
        outf.write(self._format(_body))
0.175.13 by John Arbash Meinel
Add an entry in the body to make it easier to find the url
90
0.171.55 by Jelmer Vernooij
Use config stacks.
91
        log_format = self.config.get('post_commit_log_format')
92
        lf = log.log_formatter(log_format,
0.171.34 by John Arbash Meinel
Special case showing a single revision without a merge.
93
                               show_ids=True,
94
                               to_file=outf
95
                               )
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
96
0.171.34 by John Arbash Meinel
Special case showing a single revision without a merge.
97
        if len(self.revision.parent_ids) <= 1:
98
            # This is not a merge, so we can special case the display of one
99
            # revision, and not have to encur the show_log overhead.
100
            lr = log.LogRevision(self.revision, self.revno, 0, None)
101
            lf.log_revision(lr)
102
        else:
103
            # let the show_log code figure out what revisions need to be
104
            # displayed, as this is a merge
105
            log.show_log(self.branch,
106
                         lf,
107
                         start_revision=rev1,
108
                         end_revision=rev2,
109
                         verbose=True
110
                         )
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
111
112
        return outf.getvalue()
113
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
114
    def get_diff(self):
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
115
        """Add the diff from the commit to the output.
116
117
        If the diff has more than difflimit lines, it will be skipped.
118
        """
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
119
        difflimit = self.difflimit()
120
        if not difflimit:
121
            # No need to compute a diff if we aren't going to display it
122
            return
123
6649.1.1 by Jelmer Vernooij
Merge bzr-email plugin.
124
        from ...diff import show_diff_trees
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
125
        # optionally show the diff if its smaller than the post_commit_difflimit option
126
        revid_new = self.revision.revision_id
127
        if self.revision.parent_ids:
128
            revid_old = self.revision.parent_ids[0]
7143.15.2 by Jelmer Vernooij
Run autopep8.
129
            tree_new, tree_old = self.repository.revision_trees(
130
                (revid_new, revid_old))
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
131
        else:
132
            # revision_trees() doesn't allow None or 'null:' to be passed as a
133
            # revision. So we need to call revision_tree() twice.
134
            revid_old = _mod_revision.NULL_REVISION
0.171.24 by John Arbash Meinel
Switch to using a local repository if available,
135
            tree_new = self.repository.revision_tree(revid_new)
136
            tree_old = self.repository.revision_tree(revid_old)
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
137
6791.2.3 by Jelmer Vernooij
Fix more imports.
138
        # We can use a StringIO because show_diff_trees should only write
0.175.11 by John Arbash Meinel
Cleanup from review comments by Marius Gedminas
139
        # 8-bit strings. It is an error to write a Unicode string here.
6791.2.3 by Jelmer Vernooij
Fix more imports.
140
        from ...sixish import StringIO
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
141
        diff_content = StringIO()
0.171.55 by Jelmer Vernooij
Use config stacks.
142
        diff_options = self.config.get('post_commit_diffoptions')
0.184.1 by Sergei Golubchik
add support for post_commit_diffoptions
143
        show_diff_trees(tree_old, tree_new, diff_content, None, diff_options)
7143.15.2 by Jelmer Vernooij
Run autopep8.
144
        numlines = diff_content.getvalue().count('\n') + 1
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
145
        if numlines <= difflimit:
146
            return diff_content.getvalue()
147
        else:
148
            return ("\nDiff too large for email"
149
                    " (%d lines, the limit is %d).\n"
150
                    % (numlines, difflimit))
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
151
152
    def difflimit(self):
0.175.11 by John Arbash Meinel
Cleanup from review comments by Marius Gedminas
153
        """Maximum number of lines of diff to show."""
0.171.55 by Jelmer Vernooij
Use config stacks.
154
        return self.config.get('post_commit_difflimit')
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
155
156
    def mailer(self):
157
        """What mail program to use."""
0.171.55 by Jelmer Vernooij
Use config stacks.
158
        return self.config.get('post_commit_mailer')
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
159
160
    def _command_line(self):
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
161
        cmd = [self.mailer(), '-s', self.subject(), '-a',
7143.15.2 by Jelmer Vernooij
Run autopep8.
162
               "From: " + self.from_address()]
0.171.55 by Jelmer Vernooij
Use config stacks.
163
        cmd.extend(self.to())
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
164
        return cmd
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
165
166
    def to(self):
167
        """What is the address the mail should go to."""
0.171.55 by Jelmer Vernooij
Use config stacks.
168
        return self.config.get('post_commit_to')
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
169
170
    def url(self):
171
        """What URL to display in the subject of the mail"""
0.171.55 by Jelmer Vernooij
Use config stacks.
172
        url = self.config.get('post_commit_url')
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
173
        if url is None:
0.171.55 by Jelmer Vernooij
Use config stacks.
174
            url = self.config.get('public_branch')
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
175
        if url is None:
176
            url = self.branch.base
177
        return url
178
179
    def from_address(self):
180
        """What address should I send from."""
0.171.55 by Jelmer Vernooij
Use config stacks.
181
        result = self.config.get('post_commit_sender')
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
182
        if result is None:
0.171.55 by Jelmer Vernooij
Use config stacks.
183
            result = self.config.get('email')
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
184
        return result
185
0.178.1 by Steve Langasek
add support for a new option, 'post_commit_headers', used to specify
186
    def extra_headers(self):
187
        """Additional headers to include when sending."""
188
        result = {}
0.171.55 by Jelmer Vernooij
Use config stacks.
189
        headers = self.config.get('revision_mail_headers')
0.171.42 by Robert Collins
Merge patch from Steve Langasek adding support for arbitrary headers on revision notification emails.
190
        if not headers:
191
            return
0.178.2 by Steve Langasek
whoops, don't iterate over the characters in the string when there's only one
192
        for line in headers:
0.171.42 by Robert Collins
Merge patch from Steve Langasek adding support for arbitrary headers on revision notification emails.
193
            key, value = line.split(": ", 1)
194
            result[key] = value
0.178.1 by Steve Langasek
add support for a new option, 'post_commit_headers', used to specify
195
        return result
196
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
197
    def send(self):
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
198
        """Send the email.
199
200
        Depending on the configuration, this will either use smtplib, or it
201
        will call out to the 'mail' program.
202
        """
6754.8.4 by Jelmer Vernooij
Use new context stuff.
203
        with self.branch.lock_read(), self.repository.lock_read():
0.171.24 by John Arbash Meinel
Switch to using a local repository if available,
204
            # Do this after we have locked, to make things faster.
205
            self._setup_revision_and_revno()
206
            mailer = self.mailer()
207
            if mailer == 'smtplib':
208
                self._send_using_smtplib()
209
            else:
210
                self._send_using_process()
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
211
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
212
    def _send_using_process(self):
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
213
        """Spawn a 'mail' subprocess to send the email."""
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
214
        # TODO think up a good test for this, but I think it needs
215
        # a custom binary shipped with. RBC 20051021
0.181.1 by Martin Pool
Change subprocess mail-sender to use a tempfile rather than a pipe
216
        msgfile = tempfile.NamedTemporaryFile()
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
217
        try:
0.181.1 by Martin Pool
Change subprocess mail-sender to use a tempfile rather than a pipe
218
            msgfile.write(self.body().encode('utf8'))
0.171.46 by Martin Pool
Write outgoing message to a file to try to avoid deadlocks
219
            diff = self.get_diff()
220
            if diff:
221
                msgfile.write(diff)
0.181.1 by Martin Pool
Change subprocess mail-sender to use a tempfile rather than a pipe
222
            msgfile.flush()
223
            msgfile.seek(0)
224
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
225
            process = subprocess.Popen(self._command_line(),
7143.15.2 by Jelmer Vernooij
Run autopep8.
226
                                       stdin=msgfile.fileno())
0.181.1 by Martin Pool
Change subprocess mail-sender to use a tempfile rather than a pipe
227
228
            rc = process.wait()
229
            if rc != 0:
7143.15.2 by Jelmer Vernooij
Run autopep8.
230
                raise errors.BzrError(
231
                    "Failed to send email: exit status %s" % (rc,))
0.181.1 by Martin Pool
Change subprocess mail-sender to use a tempfile rather than a pipe
232
        finally:
233
            msgfile.close()
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
234
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
235
    def _send_using_smtplib(self):
236
        """Use python's smtplib to send the email."""
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
237
        body = self.body()
238
        diff = self.get_diff()
239
        subject = self.subject()
240
        from_addr = self.from_address()
241
        to_addrs = self.to()
6674.1.1 by Jelmer Vernooij
Clean up email plugin.
242
        header = self.extra_headers()
243
0.187.1 by Robert J. Tanner
Fix for Bug #983983 https://bugs.launchpad.net/bzr-email/+bug/983983
244
        msg = EmailMessage(from_addr, to_addrs, subject, body)
6674.1.1 by Jelmer Vernooij
Clean up email plugin.
245
0.187.1 by Robert J. Tanner
Fix for Bug #983983 https://bugs.launchpad.net/bzr-email/+bug/983983
246
        if diff:
247
            msg.add_inline_attachment(diff, self.diff_filename())
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
248
0.187.1 by Robert J. Tanner
Fix for Bug #983983 https://bugs.launchpad.net/bzr-email/+bug/983983
249
        # Add revision_mail_headers to the headers
7183.3.1 by Martin
Fix E71* lint errors
250
        if header is None:
0.187.2 by Robert J. Tanner
Fixed issue revision_mail_headers not defined in .bazaar/bazaar.conf
251
            for k, v in header.items():
252
                msg[k] = v
6674.1.1 by Jelmer Vernooij
Clean up email plugin.
253
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
254
        smtp = self._smtplib_implementation(self.config)
0.187.1 by Robert J. Tanner
Fix for Bug #983983 https://bugs.launchpad.net/bzr-email/+bug/983983
255
        smtp.send_email(msg)
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
256
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
257
    def should_send(self):
0.171.55 by Jelmer Vernooij
Use config stacks.
258
        post_commit_push_pull = self.config.get('post_commit_push_pull')
0.171.39 by Robert Collins
Draft support for mailing on push/pull.
259
        if post_commit_push_pull and self.op == 'commit':
260
            # We will be called again with a push op, send the mail then.
261
            return False
262
        if not post_commit_push_pull and self.op != 'commit':
263
            # Mailing on commit only, and this is a push/pull operation.
264
            return False
0.177.2 by Vincent Ladeuil
Fixed as per Robert's review.
265
        return bool(self.to() and self.from_address())
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
266
267
    def send_maybe(self):
268
        if self.should_send():
269
            self.send()
0.171.51 by Jelmer Vernooij
Avoid double lookup of revision, remove some unnecessary whitespace.
270
271
    def subject(self):
0.171.55 by Jelmer Vernooij
Use config stacks.
272
        _subject = self.config.get('post_commit_subject')
0.186.4 by Renato Silva
Small code fixes requested by Jelmer Vernooij for merging
273
        if _subject is None:
7143.15.2 by Jelmer Vernooij
Run autopep8.
274
            _subject = ("Rev %d: %s in %s" %
275
                        (self.revno,
276
                         self.revision.get_summary(),
277
                         self.url()))
0.186.4 by Renato Silva
Small code fixes requested by Jelmer Vernooij for merging
278
        return self._format(_subject)
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
279
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
280
    def diff_filename(self):
281
        return "patch-%s.diff" % (self.revno,)
282
0.171.55 by Jelmer Vernooij
Use config stacks.
283
284
opt_post_commit_body = Option("post_commit_body",
7143.15.2 by Jelmer Vernooij
Run autopep8.
285
                              help="Body for post commit emails.")
0.171.55 by Jelmer Vernooij
Use config stacks.
286
opt_post_commit_subject = Option("post_commit_subject",
7143.15.2 by Jelmer Vernooij
Run autopep8.
287
                                 help="Subject for post commit emails.")
0.171.55 by Jelmer Vernooij
Use config stacks.
288
opt_post_commit_log_format = Option('post_commit_log_format',
7143.15.2 by Jelmer Vernooij
Run autopep8.
289
                                    default='long', help="Log format for option.")
0.171.55 by Jelmer Vernooij
Use config stacks.
290
opt_post_commit_difflimit = Option('post_commit_difflimit',
7143.15.2 by Jelmer Vernooij
Run autopep8.
291
                                   default=1000, from_unicode=int_from_store,
292
                                   help="Maximum number of lines in diffs.")
0.171.55 by Jelmer Vernooij
Use config stacks.
293
opt_post_commit_push_pull = Option('post_commit_push_pull',
7143.15.2 by Jelmer Vernooij
Run autopep8.
294
                                   from_unicode=bool_from_store,
295
                                   help="Whether to send emails on push and pull.")
0.171.55 by Jelmer Vernooij
Use config stacks.
296
opt_post_commit_diffoptions = Option('post_commit_diffoptions',
7143.15.2 by Jelmer Vernooij
Run autopep8.
297
                                     help="Diff options to use.")
0.171.55 by Jelmer Vernooij
Use config stacks.
298
opt_post_commit_sender = Option('post_commit_sender',
7143.15.2 by Jelmer Vernooij
Run autopep8.
299
                                help='From address to use for emails.')
0.171.55 by Jelmer Vernooij
Use config stacks.
300
opt_post_commit_to = ListOption('post_commit_to',
7143.15.2 by Jelmer Vernooij
Run autopep8.
301
                                help='Address to send commit emails to.')
0.171.55 by Jelmer Vernooij
Use config stacks.
302
opt_post_commit_mailer = Option('post_commit_mailer',
7143.15.2 by Jelmer Vernooij
Run autopep8.
303
                                help='Mail client to use.', default='mail')
0.171.55 by Jelmer Vernooij
Use config stacks.
304
opt_post_commit_url = Option('post_commit_url',
7143.15.2 by Jelmer Vernooij
Run autopep8.
305
                             help='URL to mention for branch in post commit messages.')
0.171.55 by Jelmer Vernooij
Use config stacks.
306
opt_revision_mail_headers = ListOption('revision_mail_headers',
7143.15.2 by Jelmer Vernooij
Run autopep8.
307
                                       help="Extra revision headers.")