/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
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 errno
18
import subprocess
19
20
from bzrlib import (
21
    errors,
22
    revision as _mod_revision,
23
    )
24
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
25
from smtp_connection import SMTPConnection
26
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
27
28
class EmailSender(object):
29
    """An email message sender."""
30
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
31
    _smtplib_implementation = SMTPConnection
32
0.171.39 by Robert Collins
Draft support for mailing on push/pull.
33
    def __init__(self, branch, revision_id, config, local_branch=None,
34
        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.
35
        self.config = config
36
        self.branch = branch
0.171.24 by John Arbash Meinel
Switch to using a local repository if available,
37
        self.repository = branch.repository
38
        if (local_branch is not None and
39
            local_branch.repository.has_revision(revision_id)):
40
            self.repository = local_branch.repository
41
        self._revision_id = revision_id
42
        self.revision = None
43
        self.revno = None
0.171.39 by Robert Collins
Draft support for mailing on push/pull.
44
        self.op = op
0.185.1 by Renato Silva
New user options for email subject and body.
45
        self.committer = self.repository.get_revision(self._revision_id).committer
0.171.24 by John Arbash Meinel
Switch to using a local repository if available,
46
47
    def _setup_revision_and_revno(self):
48
        self.revision = self.repository.get_revision(self._revision_id)
49
        self.revno = self.branch.revision_id_to_revno(self._revision_id)
0.185.1 by Renato Silva
New user options for email subject and body.
50
        
51
    def format(self, text):
52
        if text == None:
53
            return None
54
        fields = {
55
            'committer': self.committer,
56
            'message': self.revision.get_summary(),
57
            'revision': '%d' % self.revno,            
58
            'url': self.url()            
59
        }        
60
        for name, value in fields.items():
61
            text = text.replace('$%s' % name, value)                 
0.185.2 by Renato Silva
Fix for new line processing.
62
        return text.replace('\\n', '\n')
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
63
64
    def body(self):
0.171.34 by John Arbash Meinel
Special case showing a single revision without a merge.
65
        from bzrlib 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.
66
67
        rev1 = rev2 = self.revno
68
        if rev1 == 0:
69
            rev1 = None
70
            rev2 = None
71
72
        # use 'replace' so that we don't abort if trying to write out
73
        # in e.g. the default C locale.
74
0.175.11 by John Arbash Meinel
Cleanup from review comments by Marius Gedminas
75
        # We must use StringIO.StringIO because we want a Unicode string that
76
        # 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.
77
        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.
78
        outf = StringIO()
0.175.13 by John Arbash Meinel
Add an entry in the body to make it easier to find the url
79
0.185.1 by Renato Silva
New user options for email subject and body.
80
        _body = self.config.get_user_option('post_commit_body')
81
        if _body == None:
82
            _body = 'At %s\n\n' % self.url()            
83
        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
84
0.171.34 by John Arbash Meinel
Special case showing a single revision without a merge.
85
        lf = log.log_formatter('long',
86
                               show_ids=True,
87
                               to_file=outf
88
                               )
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
89
0.171.34 by John Arbash Meinel
Special case showing a single revision without a merge.
90
        if len(self.revision.parent_ids) <= 1:
91
            # This is not a merge, so we can special case the display of one
92
            # revision, and not have to encur the show_log overhead.
93
            lr = log.LogRevision(self.revision, self.revno, 0, None)
94
            lf.log_revision(lr)
95
        else:
96
            # let the show_log code figure out what revisions need to be
97
            # displayed, as this is a merge
98
            log.show_log(self.branch,
99
                         lf,
100
                         start_revision=rev1,
101
                         end_revision=rev2,
102
                         verbose=True
103
                         )
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
104
105
        return outf.getvalue()
106
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
107
    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.
108
        """Add the diff from the commit to the output.
109
110
        If the diff has more than difflimit lines, it will be skipped.
111
        """
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
112
        difflimit = self.difflimit()
113
        if not difflimit:
114
            # No need to compute a diff if we aren't going to display it
115
            return
116
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
117
        from bzrlib.diff import show_diff_trees
118
        # optionally show the diff if its smaller than the post_commit_difflimit option
119
        revid_new = self.revision.revision_id
120
        if self.revision.parent_ids:
121
            revid_old = self.revision.parent_ids[0]
0.171.24 by John Arbash Meinel
Switch to using a local repository if available,
122
            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.
123
        else:
124
            # revision_trees() doesn't allow None or 'null:' to be passed as a
125
            # revision. So we need to call revision_tree() twice.
126
            revid_old = _mod_revision.NULL_REVISION
0.171.24 by John Arbash Meinel
Switch to using a local repository if available,
127
            tree_new = self.repository.revision_tree(revid_new)
128
            tree_old = self.repository.revision_tree(revid_old)
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
129
0.175.11 by John Arbash Meinel
Cleanup from review comments by Marius Gedminas
130
        # We can use a cStringIO because show_diff_trees should only write
131
        # 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.
132
        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.
133
        diff_content = StringIO()
134
        show_diff_trees(tree_old, tree_new, diff_content)
0.175.11 by John Arbash Meinel
Cleanup from review comments by Marius Gedminas
135
        numlines = diff_content.getvalue().count('\n')+1
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
136
        if numlines <= difflimit:
137
            return diff_content.getvalue()
138
        else:
139
            return ("\nDiff too large for email"
140
                    " (%d lines, the limit is %d).\n"
141
                    % (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.
142
143
    def difflimit(self):
0.175.11 by John Arbash Meinel
Cleanup from review comments by Marius Gedminas
144
        """Maximum number of lines of diff to show."""
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
145
        result = self.config.get_user_option('post_commit_difflimit')
146
        if result is None:
147
            result = 1000
148
        return int(result)
149
150
    def mailer(self):
151
        """What mail program to use."""
152
        result = self.config.get_user_option('post_commit_mailer')
153
        if result is None:
154
            result = "mail"
155
        return result
156
157
    def _command_line(self):
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
158
        cmd = [self.mailer(), '-s', self.subject(), '-a',
159
                "From: " + self.from_address()]
160
        to = self.to()
161
        if isinstance(to, basestring):
162
            cmd.append(to)
163
        else:
164
            cmd.extend(to)
165
        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.
166
167
    def to(self):
168
        """What is the address the mail should go to."""
169
        return self.config.get_user_option('post_commit_to')
170
171
    def url(self):
172
        """What URL to display in the subject of the mail"""
173
        url = self.config.get_user_option('post_commit_url')
174
        if url is None:
175
            url = self.config.get_user_option('public_branch')
176
        if url is None:
177
            url = self.branch.base
178
        return url
179
180
    def from_address(self):
181
        """What address should I send from."""
182
        result = self.config.get_user_option('post_commit_sender')
183
        if result is None:
184
            result = self.config.username()
185
        return result
186
187
    def send(self):
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
188
        """Send the email.
189
190
        Depending on the configuration, this will either use smtplib, or it
191
        will call out to the 'mail' program.
192
        """
0.171.24 by John Arbash Meinel
Switch to using a local repository if available,
193
        self.branch.lock_read()
194
        self.repository.lock_read()
195
        try:
196
            # Do this after we have locked, to make things faster.
197
            self._setup_revision_and_revno()
198
            mailer = self.mailer()
199
            if mailer == 'smtplib':
200
                self._send_using_smtplib()
201
            else:
202
                self._send_using_process()
203
        finally:
204
            self.repository.unlock()
205
            self.branch.unlock()
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
206
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
207
    def _send_using_process(self):
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
208
        """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.
209
        # TODO think up a good test for this, but I think it needs
210
        # a custom binary shipped with. RBC 20051021
211
        try:
212
            process = subprocess.Popen(self._command_line(),
213
                                       stdin=subprocess.PIPE)
214
            try:
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
215
                message = self.body().encode('utf8') + self.get_diff()
216
                result = process.communicate(message)[0]
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
217
                if process.returncode is None:
218
                    process.wait()
219
                if process.returncode != 0:
220
                    raise errors.BzrError("Failed to send email")
221
                return result
222
            except OSError, e:
223
                if e.errno == errno.EPIPE:
224
                    raise errors.BzrError("Failed to send email.")
225
                else:
226
                    raise
227
        except ValueError:
228
            # bad subprocess parameters, should never happen.
229
            raise
230
        except OSError, e:
231
            if e.errno == errno.ENOENT:
232
                raise errors.BzrError("mail is not installed !?")
233
            else:
234
                raise
235
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
236
    def _send_using_smtplib(self):
237
        """Use python's smtplib to send the email."""
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
238
        body = self.body()
239
        diff = self.get_diff()
240
        subject = self.subject()
241
        from_addr = self.from_address()
242
        to_addrs = self.to()
243
        if isinstance(to_addrs, basestring):
244
            to_addrs = [to_addrs]
245
246
        smtp = self._smtplib_implementation(self.config)
247
        smtp.send_text_and_attachment_email(from_addr, to_addrs,
248
                                            subject, body, diff,
249
                                            self.diff_filename())
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
250
0.175.3 by John Arbash Meinel
Move guts into another file to improve startup time, fix bug when old revid is None.
251
    def should_send(self):
0.171.39 by Robert Collins
Draft support for mailing on push/pull.
252
        result = self.config.get_user_option('post_commit_difflimit')
253
        post_commit_push_pull = self.config.get_user_option(
254
            'post_commit_push_pull') == 'True'
255
        if post_commit_push_pull and self.op == 'commit':
256
            # We will be called again with a push op, send the mail then.
257
            return False
258
        if not post_commit_push_pull and self.op != 'commit':
259
            # Mailing on commit only, and this is a push/pull operation.
260
            return False
0.177.2 by Vincent Ladeuil
Fixed as per Robert's review.
261
        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.
262
263
    def send_maybe(self):
264
        if self.should_send():
265
            self.send()
0.185.1 by Renato Silva
New user options for email subject and body.
266
            
267
    def subject(self):        
268
        _subject = self.config.get_user_option('post_commit_subject')
269
        if _subject == None:
270
            _subject = ("Rev %d: %s in %s" % 
0.185.3 by Renato Silva
Removed external references for fields dict.
271
                (self.revno,
272
                 self.revision.get_summary(),
273
                 self.url()))
0.185.1 by Renato Silva
New user options for email subject and body.
274
        return self.format(_subject)
0.175.4 by John Arbash Meinel
Add a class for handling emails properly.
275
0.175.7 by John Arbash Meinel
split out SMTPConnection to its own file.
276
    def diff_filename(self):
277
        return "patch-%s.diff" % (self.revno,)
278