/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,
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
48
            local_branch.repository.has_revision(revision_id)):
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.
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
83
        from StringIO 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]
0.171.24 by John Arbash Meinel
Switch to using a local repository if available,
129
            tree_new, tree_old = self.repository.revision_trees((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.
130
        else:
131
            # revision_trees() doesn't allow None or 'null:' to be passed as a
132
            # revision. So we need to call revision_tree() twice.
133
            revid_old = _mod_revision.NULL_REVISION
0.171.24 by John Arbash Meinel
Switch to using a local repository if available,
134
            tree_new = self.repository.revision_tree(revid_new)
135
            tree_old = self.repository.revision_tree(revid_old)
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
136
0.175.11 by John Arbash Meinel
Cleanup from review comments by Marius Gedminas
137
        # We can use a cStringIO because show_diff_trees should only write
138
        # 8-bit strings. It is an error to write a Unicode string here.
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
139
        from cStringIO 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.
140
        diff_content = StringIO()
0.171.55 by Jelmer Vernooij
Use config stacks.
141
        diff_options = self.config.get('post_commit_diffoptions')
0.184.1 by Sergei Golubchik
add support for post_commit_diffoptions
142
        show_diff_trees(tree_old, tree_new, diff_content, None, diff_options)
0.175.11 by John Arbash Meinel
Cleanup from review comments by Marius Gedminas
143
        numlines = diff_content.getvalue().count('\n')+1
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
144
        if numlines <= difflimit:
145
            return diff_content.getvalue()
146
        else:
147
            return ("\nDiff too large for email"
148
                    " (%d lines, the limit is %d).\n"
149
                    % (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.
150
151
    def difflimit(self):
0.175.11 by John Arbash Meinel
Cleanup from review comments by Marius Gedminas
152
        """Maximum number of lines of diff to show."""
0.171.55 by Jelmer Vernooij
Use config stacks.
153
        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.
154
155
    def mailer(self):
156
        """What mail program to use."""
0.171.55 by Jelmer Vernooij
Use config stacks.
157
        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.
158
159
    def _command_line(self):
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
160
        cmd = [self.mailer(), '-s', self.subject(), '-a',
161
                "From: " + self.from_address()]
0.171.55 by Jelmer Vernooij
Use config stacks.
162
        cmd.extend(self.to())
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
163
        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.
164
165
    def to(self):
166
        """What is the address the mail should go to."""
0.171.55 by Jelmer Vernooij
Use config stacks.
167
        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.
168
169
    def url(self):
170
        """What URL to display in the subject of the mail"""
0.171.55 by Jelmer Vernooij
Use config stacks.
171
        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.
172
        if url is None:
0.171.55 by Jelmer Vernooij
Use config stacks.
173
            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.
174
        if url is None:
175
            url = self.branch.base
176
        return url
177
178
    def from_address(self):
179
        """What address should I send from."""
0.171.55 by Jelmer Vernooij
Use config stacks.
180
        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.
181
        if result is None:
0.171.55 by Jelmer Vernooij
Use config stacks.
182
            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.
183
        return result
184
0.178.1 by Steve Langasek
add support for a new option, 'post_commit_headers', used to specify
185
    def extra_headers(self):
186
        """Additional headers to include when sending."""
187
        result = {}
0.171.55 by Jelmer Vernooij
Use config stacks.
188
        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.
189
        if not headers:
190
            return
0.178.2 by Steve Langasek
whoops, don't iterate over the characters in the string when there's only one
191
        for line in headers:
0.171.42 by Robert Collins
Merge patch from Steve Langasek adding support for arbitrary headers on revision notification emails.
192
            key, value = line.split(": ", 1)
193
            result[key] = value
0.178.1 by Steve Langasek
add support for a new option, 'post_commit_headers', used to specify
194
        return result
195
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
196
    def send(self):
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
197
        """Send the email.
198
199
        Depending on the configuration, this will either use smtplib, or it
200
        will call out to the 'mail' program.
201
        """
0.171.24 by John Arbash Meinel
Switch to using a local repository if available,
202
        self.branch.lock_read()
203
        self.repository.lock_read()
204
        try:
205
            # Do this after we have locked, to make things faster.
206
            self._setup_revision_and_revno()
207
            mailer = self.mailer()
208
            if mailer == 'smtplib':
209
                self._send_using_smtplib()
210
            else:
211
                self._send_using_process()
212
        finally:
213
            self.repository.unlock()
214
            self.branch.unlock()
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
215
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
216
    def _send_using_process(self):
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
217
        """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.
218
        # TODO think up a good test for this, but I think it needs
219
        # 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
220
        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.
221
        try:
0.181.1 by Martin Pool
Change subprocess mail-sender to use a tempfile rather than a pipe
222
            msgfile.write(self.body().encode('utf8'))
0.171.46 by Martin Pool
Write outgoing message to a file to try to avoid deadlocks
223
            diff = self.get_diff()
224
            if diff:
225
                msgfile.write(diff)
0.181.1 by Martin Pool
Change subprocess mail-sender to use a tempfile rather than a pipe
226
            msgfile.flush()
227
            msgfile.seek(0)
228
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
229
            process = subprocess.Popen(self._command_line(),
0.181.1 by Martin Pool
Change subprocess mail-sender to use a tempfile rather than a pipe
230
                stdin=msgfile.fileno())
231
232
            rc = process.wait()
233
            if rc != 0:
234
                raise errors.BzrError("Failed to send email: exit status %s" % (rc,))
235
        finally:
236
            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.
237
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
238
    def _send_using_smtplib(self):
239
        """Use python's smtplib to send the email."""
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
240
        body = self.body()
241
        diff = self.get_diff()
242
        subject = self.subject()
243
        from_addr = self.from_address()
244
        to_addrs = self.to()
245
        if isinstance(to_addrs, basestring):
246
            to_addrs = [to_addrs]
6674.1.1 by Jelmer Vernooij
Clean up email plugin.
247
        header = self.extra_headers()
248
0.187.1 by Robert J. Tanner
Fix for Bug #983983 https://bugs.launchpad.net/bzr-email/+bug/983983
249
        msg = EmailMessage(from_addr, to_addrs, subject, body)
6674.1.1 by Jelmer Vernooij
Clean up email plugin.
250
0.187.1 by Robert J. Tanner
Fix for Bug #983983 https://bugs.launchpad.net/bzr-email/+bug/983983
251
        if diff:
252
            msg.add_inline_attachment(diff, self.diff_filename())
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
253
0.187.1 by Robert J. Tanner
Fix for Bug #983983 https://bugs.launchpad.net/bzr-email/+bug/983983
254
        # Add revision_mail_headers to the headers
0.187.2 by Robert J. Tanner
Fixed issue revision_mail_headers not defined in .bazaar/bazaar.conf
255
        if header != None:
256
            for k, v in header.items():
257
                msg[k] = v
6674.1.1 by Jelmer Vernooij
Clean up email plugin.
258
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
259
        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
260
        smtp.send_email(msg)
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
261
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
262
    def should_send(self):
0.171.55 by Jelmer Vernooij
Use config stacks.
263
        post_commit_push_pull = self.config.get('post_commit_push_pull')
0.171.39 by Robert Collins
Draft support for mailing on push/pull.
264
        if post_commit_push_pull and self.op == 'commit':
265
            # We will be called again with a push op, send the mail then.
266
            return False
267
        if not post_commit_push_pull and self.op != 'commit':
268
            # Mailing on commit only, and this is a push/pull operation.
269
            return False
0.177.2 by Vincent Ladeuil
Fixed as per Robert's review.
270
        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.
271
272
    def send_maybe(self):
273
        if self.should_send():
274
            self.send()
0.171.51 by Jelmer Vernooij
Avoid double lookup of revision, remove some unnecessary whitespace.
275
276
    def subject(self):
0.171.55 by Jelmer Vernooij
Use config stacks.
277
        _subject = self.config.get('post_commit_subject')
0.186.4 by Renato Silva
Small code fixes requested by Jelmer Vernooij for merging
278
        if _subject is None:
0.185.1 by Renato Silva
New user options for email subject and body.
279
            _subject = ("Rev %d: %s in %s" % 
0.185.3 by Renato Silva
Removed external references for fields dict.
280
                (self.revno,
281
                 self.revision.get_summary(),
282
                 self.url()))
0.186.4 by Renato Silva
Small code fixes requested by Jelmer Vernooij for merging
283
        return self._format(_subject)
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
284
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
285
    def diff_filename(self):
286
        return "patch-%s.diff" % (self.revno,)
287
0.171.55 by Jelmer Vernooij
Use config stacks.
288
289
opt_post_commit_body = Option("post_commit_body",
290
    help="Body for post commit emails.")
291
opt_post_commit_subject = Option("post_commit_subject",
292
    help="Subject for post commit emails.")
293
opt_post_commit_log_format = Option('post_commit_log_format',
294
    default='long', help="Log format for option.")
295
opt_post_commit_difflimit = Option('post_commit_difflimit',
296
    default=1000, from_unicode=int_from_store,
297
    help="Maximum number of lines in diffs.")
298
opt_post_commit_push_pull = Option('post_commit_push_pull',
299
    from_unicode=bool_from_store,
300
    help="Whether to send emails on push and pull.")
301
opt_post_commit_diffoptions = Option('post_commit_diffoptions',
302
    help="Diff options to use.")
303
opt_post_commit_sender = Option('post_commit_sender',
304
    help='From address to use for emails.')
305
opt_post_commit_to = ListOption('post_commit_to',
306
    help='Address to send commit emails to.')
307
opt_post_commit_mailer = Option('post_commit_mailer',
308
    help='Mail client to use.', default='mail')
309
opt_post_commit_url = Option('post_commit_url',
310
    help='URL to mention for branch in post commit messages.')
311
opt_revision_mail_headers = ListOption('revision_mail_headers',
312
    help="Extra revision headers.")