/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.
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]
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
6791.2.3 by Jelmer Vernooij
Fix more imports.
137
        # 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
138
        # 8-bit strings. It is an error to write a Unicode string here.
6791.2.3 by Jelmer Vernooij
Fix more imports.
139
        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.
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
        """
6754.8.4 by Jelmer Vernooij
Use new context stuff.
202
        with self.branch.lock_read(), self.repository.lock_read():
0.171.24 by John Arbash Meinel
Switch to using a local repository if available,
203
            # Do this after we have locked, to make things faster.
204
            self._setup_revision_and_revno()
205
            mailer = self.mailer()
206
            if mailer == 'smtplib':
207
                self._send_using_smtplib()
208
            else:
209
                self._send_using_process()
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
210
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
211
    def _send_using_process(self):
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
212
        """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.
213
        # TODO think up a good test for this, but I think it needs
214
        # 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
215
        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.
216
        try:
0.181.1 by Martin Pool
Change subprocess mail-sender to use a tempfile rather than a pipe
217
            msgfile.write(self.body().encode('utf8'))
0.171.46 by Martin Pool
Write outgoing message to a file to try to avoid deadlocks
218
            diff = self.get_diff()
219
            if diff:
220
                msgfile.write(diff)
0.181.1 by Martin Pool
Change subprocess mail-sender to use a tempfile rather than a pipe
221
            msgfile.flush()
222
            msgfile.seek(0)
223
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
224
            process = subprocess.Popen(self._command_line(),
0.181.1 by Martin Pool
Change subprocess mail-sender to use a tempfile rather than a pipe
225
                stdin=msgfile.fileno())
226
227
            rc = process.wait()
228
            if rc != 0:
229
                raise errors.BzrError("Failed to send email: exit status %s" % (rc,))
230
        finally:
231
            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.
232
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
233
    def _send_using_smtplib(self):
234
        """Use python's smtplib to send the email."""
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
235
        body = self.body()
236
        diff = self.get_diff()
237
        subject = self.subject()
238
        from_addr = self.from_address()
239
        to_addrs = self.to()
6674.1.1 by Jelmer Vernooij
Clean up email plugin.
240
        header = self.extra_headers()
241
0.187.1 by Robert J. Tanner
Fix for Bug #983983 https://bugs.launchpad.net/bzr-email/+bug/983983
242
        msg = EmailMessage(from_addr, to_addrs, subject, body)
6674.1.1 by Jelmer Vernooij
Clean up email plugin.
243
0.187.1 by Robert J. Tanner
Fix for Bug #983983 https://bugs.launchpad.net/bzr-email/+bug/983983
244
        if diff:
245
            msg.add_inline_attachment(diff, self.diff_filename())
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
246
0.187.1 by Robert J. Tanner
Fix for Bug #983983 https://bugs.launchpad.net/bzr-email/+bug/983983
247
        # 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
248
        if header != None:
249
            for k, v in header.items():
250
                msg[k] = v
6674.1.1 by Jelmer Vernooij
Clean up email plugin.
251
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
252
        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
253
        smtp.send_email(msg)
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
254
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
255
    def should_send(self):
0.171.55 by Jelmer Vernooij
Use config stacks.
256
        post_commit_push_pull = self.config.get('post_commit_push_pull')
0.171.39 by Robert Collins
Draft support for mailing on push/pull.
257
        if post_commit_push_pull and self.op == 'commit':
258
            # We will be called again with a push op, send the mail then.
259
            return False
260
        if not post_commit_push_pull and self.op != 'commit':
261
            # Mailing on commit only, and this is a push/pull operation.
262
            return False
0.177.2 by Vincent Ladeuil
Fixed as per Robert's review.
263
        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.
264
265
    def send_maybe(self):
266
        if self.should_send():
267
            self.send()
0.171.51 by Jelmer Vernooij
Avoid double lookup of revision, remove some unnecessary whitespace.
268
269
    def subject(self):
0.171.55 by Jelmer Vernooij
Use config stacks.
270
        _subject = self.config.get('post_commit_subject')
0.186.4 by Renato Silva
Small code fixes requested by Jelmer Vernooij for merging
271
        if _subject is None:
0.185.1 by Renato Silva
New user options for email subject and body.
272
            _subject = ("Rev %d: %s in %s" % 
0.185.3 by Renato Silva
Removed external references for fields dict.
273
                (self.revno,
274
                 self.revision.get_summary(),
275
                 self.url()))
0.186.4 by Renato Silva
Small code fixes requested by Jelmer Vernooij for merging
276
        return self._format(_subject)
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
277
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
278
    def diff_filename(self):
279
        return "patch-%s.diff" % (self.revno,)
280
0.171.55 by Jelmer Vernooij
Use config stacks.
281
282
opt_post_commit_body = Option("post_commit_body",
283
    help="Body for post commit emails.")
284
opt_post_commit_subject = Option("post_commit_subject",
285
    help="Subject for post commit emails.")
286
opt_post_commit_log_format = Option('post_commit_log_format',
287
    default='long', help="Log format for option.")
288
opt_post_commit_difflimit = Option('post_commit_difflimit',
289
    default=1000, from_unicode=int_from_store,
290
    help="Maximum number of lines in diffs.")
291
opt_post_commit_push_pull = Option('post_commit_push_pull',
292
    from_unicode=bool_from_store,
293
    help="Whether to send emails on push and pull.")
294
opt_post_commit_diffoptions = Option('post_commit_diffoptions',
295
    help="Diff options to use.")
296
opt_post_commit_sender = Option('post_commit_sender',
297
    help='From address to use for emails.')
298
opt_post_commit_to = ListOption('post_commit_to',
299
    help='Address to send commit emails to.')
300
opt_post_commit_mailer = Option('post_commit_mailer',
301
    help='Mail client to use.', default='mail')
302
opt_post_commit_url = Option('post_commit_url',
303
    help='URL to mention for branch in post commit messages.')
304
opt_revision_mail_headers = ListOption('revision_mail_headers',
305
    help="Extra revision headers.")