##// END OF EJS Templates
make error better.
Vadim Gelfer -
r2788:6b27a712 default
parent child Browse files
Show More
@@ -1,276 +1,276 b''
1 1 # notify.py - email notifications for mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7 #
8 8 # hook extension to email notifications to people when changesets are
9 9 # committed to a repo they subscribe to.
10 10 #
11 11 # default mode is to print messages to stdout, for testing and
12 12 # configuring.
13 13 #
14 14 # to use, configure notify extension and enable in hgrc like this:
15 15 #
16 16 # [extensions]
17 17 # hgext.notify =
18 18 #
19 19 # [hooks]
20 20 # # one email for each incoming changeset
21 21 # incoming.notify = python:hgext.notify.hook
22 22 # # batch emails when many changesets incoming at one time
23 23 # changegroup.notify = python:hgext.notify.hook
24 24 #
25 25 # [notify]
26 26 # # config items go in here
27 27 #
28 28 # config items:
29 29 #
30 30 # REQUIRED:
31 31 # config = /path/to/file # file containing subscriptions
32 32 #
33 33 # OPTIONAL:
34 34 # test = True # print messages to stdout for testing
35 35 # strip = 3 # number of slashes to strip for url paths
36 36 # domain = example.com # domain to use if committer missing domain
37 37 # style = ... # style file to use when formatting email
38 38 # template = ... # template to use when formatting email
39 39 # incoming = ... # template to use when run as incoming hook
40 40 # changegroup = ... # template when run as changegroup hook
41 41 # maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
42 42 # maxsubject = 67 # truncate subject line longer than this
43 43 # sources = serve # notify if source of incoming changes in this list
44 44 # # (serve == ssh or http, push, pull, bundle)
45 45 # [email]
46 46 # from = user@host.com # email address to send as if none given
47 47 # [web]
48 48 # baseurl = http://hgserver/... # root of hg web site for browsing commits
49 49 #
50 50 # notify config file has same format as regular hgrc. it has two
51 51 # sections so you can express subscriptions in whatever way is handier
52 52 # for you.
53 53 #
54 54 # [usersubs]
55 55 # # key is subscriber email, value is ","-separated list of glob patterns
56 56 # user@host = pattern
57 57 #
58 58 # [reposubs]
59 59 # # key is glob pattern, value is ","-separated list of subscriber emails
60 60 # pattern = user@host
61 61 #
62 62 # glob patterns are matched against path to repo root.
63 63 #
64 64 # if you like, you can put notify config file in repo that users can
65 65 # push changes to, they can manage their own subscriptions.
66 66
67 67 from mercurial.demandload import *
68 68 from mercurial.i18n import gettext as _
69 69 from mercurial.node import *
70 70 demandload(globals(), 'email.Parser mercurial:commands,templater,util')
71 71 demandload(globals(), 'fnmatch socket time')
72 72
73 73 # template for single changeset can include email headers.
74 74 single_template = '''
75 75 Subject: changeset in {webroot}: {desc|firstline|strip}
76 76 From: {author}
77 77
78 78 changeset {node|short} in {root}
79 79 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
80 80 description:
81 81 \t{desc|tabindent|strip}
82 82 '''.lstrip()
83 83
84 84 # template for multiple changesets should not contain email headers,
85 85 # because only first set of headers will be used and result will look
86 86 # strange.
87 87 multiple_template = '''
88 88 changeset {node|short} in {root}
89 89 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
90 90 summary: {desc|firstline}
91 91 '''
92 92
93 93 deftemplates = {
94 94 'changegroup': multiple_template,
95 95 }
96 96
97 97 class notifier(object):
98 98 '''email notification class.'''
99 99
100 100 def __init__(self, ui, repo, hooktype):
101 101 self.ui = ui
102 102 cfg = self.ui.config('notify', 'config')
103 103 if cfg:
104 104 self.ui.readconfig(cfg)
105 105 self.repo = repo
106 106 self.stripcount = int(self.ui.config('notify', 'strip', 0))
107 107 self.root = self.strip(self.repo.root)
108 108 self.domain = self.ui.config('notify', 'domain')
109 109 self.sio = templater.stringio()
110 110 self.subs = self.subscribers()
111 111
112 112 mapfile = self.ui.config('notify', 'style')
113 113 template = (self.ui.config('notify', hooktype) or
114 114 self.ui.config('notify', 'template'))
115 115 self.t = templater.changeset_templater(self.ui, self.repo, mapfile,
116 116 self.sio)
117 117 if not mapfile and not template:
118 118 template = deftemplates.get(hooktype) or single_template
119 119 if template:
120 120 template = templater.parsestring(template, quoted=False)
121 121 self.t.use_template(template)
122 122
123 123 def strip(self, path):
124 124 '''strip leading slashes from local path, turn into web-safe path.'''
125 125
126 126 path = util.pconvert(path)
127 127 count = self.stripcount
128 128 while count > 0:
129 129 c = path.find('/')
130 130 if c == -1:
131 131 break
132 132 path = path[c+1:]
133 133 count -= 1
134 134 return path
135 135
136 136 def fixmail(self, addr):
137 137 '''try to clean up email addresses.'''
138 138
139 139 addr = templater.email(addr.strip())
140 140 a = addr.find('@localhost')
141 141 if a != -1:
142 142 addr = addr[:a]
143 143 if '@' not in addr:
144 144 return addr + '@' + self.domain
145 145 return addr
146 146
147 147 def subscribers(self):
148 148 '''return list of email addresses of subscribers to this repo.'''
149 149
150 150 subs = {}
151 151 for user, pats in self.ui.configitems('usersubs'):
152 152 for pat in pats.split(','):
153 153 if fnmatch.fnmatch(self.repo.root, pat.strip()):
154 154 subs[self.fixmail(user)] = 1
155 155 for pat, users in self.ui.configitems('reposubs'):
156 156 if fnmatch.fnmatch(self.repo.root, pat):
157 157 for user in users.split(','):
158 158 subs[self.fixmail(user)] = 1
159 159 subs = subs.keys()
160 160 subs.sort()
161 161 return subs
162 162
163 163 def url(self, path=None):
164 164 return self.ui.config('web', 'baseurl') + (path or self.root)
165 165
166 166 def node(self, node):
167 167 '''format one changeset.'''
168 168
169 169 self.t.show(changenode=node, changes=self.repo.changelog.read(node),
170 170 baseurl=self.ui.config('web', 'baseurl'),
171 171 root=self.repo.root,
172 172 webroot=self.root)
173 173
174 174 def skipsource(self, source):
175 175 '''true if incoming changes from this source should be skipped.'''
176 176 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
177 177 return source not in ok_sources
178 178
179 179 def send(self, node, count):
180 180 '''send message.'''
181 181
182 182 p = email.Parser.Parser()
183 183 self.sio.seek(0)
184 184 msg = p.parse(self.sio)
185 185
186 186 def fix_subject():
187 187 '''try to make subject line exist and be useful.'''
188 188
189 189 subject = msg['Subject']
190 190 if not subject:
191 191 if count > 1:
192 192 subject = _('%s: %d new changesets') % (self.root, count)
193 193 else:
194 194 changes = self.repo.changelog.read(node)
195 195 s = changes[4].lstrip().split('\n', 1)[0].rstrip()
196 196 subject = '%s: %s' % (self.root, s)
197 197 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
198 198 if maxsubject and len(subject) > maxsubject:
199 199 subject = subject[:maxsubject-3] + '...'
200 200 del msg['Subject']
201 201 msg['Subject'] = subject
202 202
203 203 def fix_sender():
204 204 '''try to make message have proper sender.'''
205 205
206 206 sender = msg['From']
207 207 if not sender:
208 208 sender = self.ui.config('email', 'from') or self.ui.username()
209 209 if '@' not in sender or '@localhost' in sender:
210 210 sender = self.fixmail(sender)
211 211 del msg['From']
212 212 msg['From'] = sender
213 213
214 214 fix_subject()
215 215 fix_sender()
216 216
217 217 msg['X-Hg-Notification'] = 'changeset ' + short(node)
218 218 if not msg['Message-Id']:
219 219 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
220 220 (short(node), int(time.time()),
221 221 hash(self.repo.root), socket.getfqdn()))
222 222 msg['To'] = ', '.join(self.subs)
223 223
224 224 msgtext = msg.as_string(0)
225 225 if self.ui.configbool('notify', 'test', True):
226 226 self.ui.write(msgtext)
227 227 if not msgtext.endswith('\n'):
228 228 self.ui.write('\n')
229 229 else:
230 230 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
231 231 (len(self.subs), count))
232 232 mail = self.ui.sendmail()
233 233 mail.sendmail(templater.email(msg['From']), self.subs, msgtext)
234 234
235 235 def diff(self, node, ref):
236 236 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
237 237 if maxdiff == 0:
238 238 return
239 239 fp = templater.stringio()
240 240 prev = self.repo.changelog.parents(node)[0]
241 241 commands.dodiff(fp, self.ui, self.repo, prev, ref)
242 242 difflines = fp.getvalue().splitlines(1)
243 243 if maxdiff > 0 and len(difflines) > maxdiff:
244 244 self.sio.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
245 245 (len(difflines), maxdiff))
246 246 difflines = difflines[:maxdiff]
247 247 elif difflines:
248 248 self.sio.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
249 249 self.sio.write(*difflines)
250 250
251 251 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
252 252 '''send email notifications to interested subscribers.
253 253
254 254 if used as changegroup hook, send one email for all changesets in
255 255 changegroup. else send one email per changeset.'''
256 256 n = notifier(ui, repo, hooktype)
257 257 if not n.subs:
258 ui.debug(_('notify: no subscribers to this repo\n'))
258 ui.debug(_('notify: no subscribers to repo %s\n' % n.root))
259 259 return
260 260 if n.skipsource(source):
261 261 ui.debug(_('notify: changes have source "%s" - skipping\n') %
262 262 source)
263 263 return
264 264 node = bin(node)
265 265 if hooktype == 'changegroup':
266 266 start = repo.changelog.rev(node)
267 267 end = repo.changelog.count()
268 268 count = end - start
269 269 for rev in xrange(start, end):
270 270 n.node(repo.changelog.node(rev))
271 271 n.diff(node, repo.changelog.tip())
272 272 else:
273 273 count = 1
274 274 n.node(node)
275 275 n.diff(node, node)
276 276 n.send(node, count)
General Comments 0
You need to be logged in to leave comments. Login now