~martin-decky/helenos/rcu

« back to all changes in this revision

Viewing changes to contrib/bazaar/bzreml/__init__.py

  • Committer: Martin Decky
  • Date: 2010-02-22 21:42:00 UTC
  • Revision ID: martin@uranus.dsrg.hide.ms.mff.cuni.cz-20100222214200-carhha3nnpb71u69
add Bazaar plugin for sending commit emails

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# Copyright (c) 2009 Martin Decky
 
3
# All rights reserved.
 
4
#
 
5
# Redistribution and use in source and binary forms, with or without
 
6
# modification, are permitted provided that the following conditions
 
7
# are met:
 
8
#
 
9
# - Redistributions of source code must retain the above copyright
 
10
#   notice, this list of conditions and the following disclaimer.
 
11
# - Redistributions in binary form must reproduce the above copyright
 
12
#   notice, this list of conditions and the following disclaimer in the
 
13
#   documentation and/or other materials provided with the distribution.
 
14
# - The name of the author may not be used to endorse or promote products
 
15
#   derived from this software without specific prior written permission.
 
16
#
 
17
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 
18
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 
19
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 
20
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 
21
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 
22
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 
23
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 
24
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 
25
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 
26
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
27
#
 
28
 
 
29
"""Send emails for commits and repository changes."""
 
30
 
 
31
#
 
32
# Inspired by bzr-email plugin (copyright (c) 2005 - 2007 Canonical Ltd.,
 
33
# distributed under GPL), but no code is shared with the original plugin.
 
34
#
 
35
# Configuration options:
 
36
#  - post_commit_to (destination email address for the commit emails)
 
37
#  - post_commit_sender (source email address for the commit emails)
 
38
#
 
39
 
 
40
import smtplib
 
41
import time
 
42
 
 
43
from StringIO import StringIO
 
44
 
 
45
from email.utils import parseaddr
 
46
from email.utils import formatdate
 
47
from email.utils import make_msgid
 
48
from email.Header import Header
 
49
from email.Message import Message
 
50
from email.mime.multipart import MIMEMultipart
 
51
from email.mime.text import MIMEText
 
52
 
 
53
from bzrlib import errors
 
54
from bzrlib import revision
 
55
from bzrlib import __version__ as bzrlib_version
 
56
 
 
57
from bzrlib.branch import Branch
 
58
from bzrlib.diff import DiffTree
 
59
 
 
60
def send_smtp(server, sender, to, subject, body):
 
61
        """Send SMTP message"""
 
62
        
 
63
        connection = smtplib.SMTP()
 
64
        
 
65
        try:
 
66
                connection.connect(server)
 
67
        except socket.error, err:
 
68
                raise errors.SocketConnectionError(host = server, msg = "Unable to connect to SMTP server", orig_error = err)
 
69
        
 
70
        sender_user, sender_email = parseaddr(sender)
 
71
        payload = MIMEText(body.encode("utf-8"), "plain", "utf-8")
 
72
        
 
73
        msg = MIMEMultipart()
 
74
        msg["From"] = "%s <%s>" % (Header(unicode(sender_user)), sender_email)
 
75
        msg["User-Agent"] = "bzr/%s" % bzrlib_version
 
76
        msg["Date"] = formatdate(None, True)
 
77
        msg["Message-Id"] = make_msgid("bzr")
 
78
        msg["To"] = to
 
79
        msg["Subject"] = Header(subject)
 
80
        msg.attach(payload)
 
81
        
 
82
        connection.sendmail(sender, [to], msg.as_string())
 
83
 
 
84
def config_to(config):
 
85
        """Address the mail should go to"""
 
86
        
 
87
        return config.get_user_option("post_commit_to")
 
88
 
 
89
def config_sender(config):
 
90
        """Address the email should be sent from"""
 
91
        
 
92
        result = config.get_user_option("post_commit_sender")
 
93
        if (result is None):
 
94
                result = config.username()
 
95
        
 
96
        return result
 
97
 
 
98
def merge_marker(revision):
 
99
        if (len(revision.parent_ids) > 1):
 
100
                return " [merge]"
 
101
        
 
102
        return ""
 
103
 
 
104
def revision_sequence(branch, revision_old_id, revision_new_id):
 
105
        """Calculate a sequence of revisions"""
 
106
        
 
107
        for revision_ac_id in branch.repository.iter_reverse_revision_history(revision_new_id):
 
108
                if (revision_ac_id == revision_old_id):
 
109
                        break
 
110
                yield revision_ac_id
 
111
 
 
112
def send_email(branch, revision_old_id, revision_new_id, config):
 
113
        """Send the email"""
 
114
        
 
115
        if (config_to(config) is not None):
 
116
                branch.lock_read()
 
117
                branch.repository.lock_read()
 
118
                try:
 
119
                        body = StringIO()
 
120
                        
 
121
                        for revision_ac_id in revision_sequence(branch, revision_old_id, revision_new_id):
 
122
                                revision_ac = branch.repository.get_revision(revision_ac_id)
 
123
                                revision_ac_no = branch.revision_id_to_revno(revision_ac_id)
 
124
                                
 
125
                                committer = revision_ac.committer
 
126
                                authors = revision_ac.get_apparent_authors()
 
127
                                date = time.strftime("%Y-%m-%d %H:%M:%S %Z (%a, %d %b %Y)", time.localtime(revision_ac.timestamp))
 
128
                                
 
129
                                if (authors != [committer]):
 
130
                                        body.write("Author: %s\n" % ", ".join(authors))
 
131
                                
 
132
                                body.write("Committer: %s\n" % committer)
 
133
                                body.write("Date: %s\n" % date)
 
134
                                body.write("New Revision: %s%s\n" % (revision_ac_no, merge_marker(revision_ac)))
 
135
                                body.write("New Id: %s\n" % revision_ac_id)
 
136
                                
 
137
                                body.write("\n")
 
138
                                
 
139
                                for parent_id in revision_ac.parent_ids:
 
140
                                        body.write("Parent: %s\n" % parent_id)
 
141
                                        
 
142
                                        tree_old = branch.repository.revision_tree(parent_id)
 
143
                                        tree_ac = branch.repository.revision_tree(revision_ac_id)
 
144
                                        
 
145
                                        delta = tree_ac.changes_from(tree_old)
 
146
                                        
 
147
                                        if (len(delta.added) > 0):
 
148
                                                body.write("Added:\n")
 
149
                                                for item in delta.added:
 
150
                                                        body.write("    %s\n" % item[0])
 
151
                                        
 
152
                                        if (len(delta.removed) > 0):
 
153
                                                body.write("Removed:\n")
 
154
                                                for item in delta.removed:
 
155
                                                        body.write("    %s\n" % item[0])
 
156
                                        
 
157
                                        if (len(delta.renamed) > 0):
 
158
                                                body.write("Renamed:\n")
 
159
                                                for item in delta.renamed:
 
160
                                                        body.write("    %s -> %s\n" % (item[0], item[1]))
 
161
                                        
 
162
                                        if (len(delta.kind_changed) > 0):
 
163
                                                body.write("Changed:\n")
 
164
                                                for item in delta.kind_changed:
 
165
                                                        body.write("    %s\n" % item[0])
 
166
                                                
 
167
                                        if (len(delta.modified) > 0):
 
168
                                                body.write("Modified:\n")
 
169
                                                for item in delta.modified:
 
170
                                                        body.write("    %s\n" % item[0])
 
171
                                        
 
172
                                        body.write("\n")
 
173
                                
 
174
                                body.write("Log:\n")
 
175
                                if (not revision_ac.message):
 
176
                                        body.write("(empty)\n")
 
177
                                else:
 
178
                                        log = revision_ac.message.rstrip("\n\r")
 
179
                                        for line in log.split("\n"):
 
180
                                                body.write("%s\n" % line)
 
181
                                
 
182
                                body.write("\n")
 
183
                        
 
184
                        tree_old = branch.repository.revision_tree(revision_old_id)
 
185
                        tree_new = branch.repository.revision_tree(revision_new_id)
 
186
                        
 
187
                        tree_old.lock_read()
 
188
                        try:
 
189
                                tree_new.lock_read()
 
190
                                try:
 
191
                                        diff = DiffTree.from_trees_options(tree_old, tree_new, body, "utf8", None, "", "", None)
 
192
                                        diff.show_diff(None, None)
 
193
                                finally:
 
194
                                        tree_new.unlock()
 
195
                        finally:
 
196
                                tree_old.unlock()
 
197
                        
 
198
                        revision_new_no = branch.revision_id_to_revno(revision_new_id)
 
199
                        
 
200
                        delta = tree_new.changes_from(tree_old)
 
201
                        files = []
 
202
                        
 
203
                        for item in delta.added:
 
204
                                files.append(item[0])
 
205
                        
 
206
                        for item in delta.removed:
 
207
                                files.append(item[0])
 
208
                        
 
209
                        for item in delta.renamed:
 
210
                                files.append(item[0])
 
211
                        
 
212
                        for item in delta.kind_changed:
 
213
                                files.append(item[0])
 
214
                        
 
215
                        for item in delta.modified:
 
216
                                files.append(item[0])
 
217
                        
 
218
                        subject = "r%d - %s" % (revision_new_no, " ".join(files))
 
219
                        
 
220
                        send_smtp("localhost", config_sender(config), config_to(config), subject, body.getvalue())
 
221
                finally:
 
222
                        branch.repository.unlock()
 
223
                        branch.unlock()
 
224
 
 
225
def branch_post_change_hook(params):
 
226
        """post_change_branch_tip hook"""
 
227
        
 
228
        send_email(params.branch, params.old_revid, params.new_revid, params.branch.get_config())
 
229
 
 
230
install_named_hook = getattr(Branch.hooks, "install_named_hook", None)
 
231
install_named_hook("post_change_branch_tip", branch_post_change_hook, "bzreml")