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