##// END OF EJS Templates
configitems: register the 'notify.template' config
Boris Feld -
r33749:ff4a11ff default
parent child Browse files
Show More
@@ -1,469 +1,472 b''
1 # notify.py - email notifications for mercurial
1 # notify.py - email notifications for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''hooks for sending email push notifications
8 '''hooks for sending email push notifications
9
9
10 This extension implements hooks to send email notifications when
10 This extension implements hooks to send email notifications when
11 changesets are sent from or received by the local repository.
11 changesets are sent from or received by the local repository.
12
12
13 First, enable the extension as explained in :hg:`help extensions`, and
13 First, enable the extension as explained in :hg:`help extensions`, and
14 register the hook you want to run. ``incoming`` and ``changegroup`` hooks
14 register the hook you want to run. ``incoming`` and ``changegroup`` hooks
15 are run when changesets are received, while ``outgoing`` hooks are for
15 are run when changesets are received, while ``outgoing`` hooks are for
16 changesets sent to another repository::
16 changesets sent to another repository::
17
17
18 [hooks]
18 [hooks]
19 # one email for each incoming changeset
19 # one email for each incoming changeset
20 incoming.notify = python:hgext.notify.hook
20 incoming.notify = python:hgext.notify.hook
21 # one email for all incoming changesets
21 # one email for all incoming changesets
22 changegroup.notify = python:hgext.notify.hook
22 changegroup.notify = python:hgext.notify.hook
23
23
24 # one email for all outgoing changesets
24 # one email for all outgoing changesets
25 outgoing.notify = python:hgext.notify.hook
25 outgoing.notify = python:hgext.notify.hook
26
26
27 This registers the hooks. To enable notification, subscribers must
27 This registers the hooks. To enable notification, subscribers must
28 be assigned to repositories. The ``[usersubs]`` section maps multiple
28 be assigned to repositories. The ``[usersubs]`` section maps multiple
29 repositories to a given recipient. The ``[reposubs]`` section maps
29 repositories to a given recipient. The ``[reposubs]`` section maps
30 multiple recipients to a single repository::
30 multiple recipients to a single repository::
31
31
32 [usersubs]
32 [usersubs]
33 # key is subscriber email, value is a comma-separated list of repo patterns
33 # key is subscriber email, value is a comma-separated list of repo patterns
34 user@host = pattern
34 user@host = pattern
35
35
36 [reposubs]
36 [reposubs]
37 # key is repo pattern, value is a comma-separated list of subscriber emails
37 # key is repo pattern, value is a comma-separated list of subscriber emails
38 pattern = user@host
38 pattern = user@host
39
39
40 A ``pattern`` is a ``glob`` matching the absolute path to a repository,
40 A ``pattern`` is a ``glob`` matching the absolute path to a repository,
41 optionally combined with a revset expression. A revset expression, if
41 optionally combined with a revset expression. A revset expression, if
42 present, is separated from the glob by a hash. Example::
42 present, is separated from the glob by a hash. Example::
43
43
44 [reposubs]
44 [reposubs]
45 */widgets#branch(release) = qa-team@example.com
45 */widgets#branch(release) = qa-team@example.com
46
46
47 This sends to ``qa-team@example.com`` whenever a changeset on the ``release``
47 This sends to ``qa-team@example.com`` whenever a changeset on the ``release``
48 branch triggers a notification in any repository ending in ``widgets``.
48 branch triggers a notification in any repository ending in ``widgets``.
49
49
50 In order to place them under direct user management, ``[usersubs]`` and
50 In order to place them under direct user management, ``[usersubs]`` and
51 ``[reposubs]`` sections may be placed in a separate ``hgrc`` file and
51 ``[reposubs]`` sections may be placed in a separate ``hgrc`` file and
52 incorporated by reference::
52 incorporated by reference::
53
53
54 [notify]
54 [notify]
55 config = /path/to/subscriptionsfile
55 config = /path/to/subscriptionsfile
56
56
57 Notifications will not be sent until the ``notify.test`` value is set
57 Notifications will not be sent until the ``notify.test`` value is set
58 to ``False``; see below.
58 to ``False``; see below.
59
59
60 Notifications content can be tweaked with the following configuration entries:
60 Notifications content can be tweaked with the following configuration entries:
61
61
62 notify.test
62 notify.test
63 If ``True``, print messages to stdout instead of sending them. Default: True.
63 If ``True``, print messages to stdout instead of sending them. Default: True.
64
64
65 notify.sources
65 notify.sources
66 Space-separated list of change sources. Notifications are activated only
66 Space-separated list of change sources. Notifications are activated only
67 when a changeset's source is in this list. Sources may be:
67 when a changeset's source is in this list. Sources may be:
68
68
69 :``serve``: changesets received via http or ssh
69 :``serve``: changesets received via http or ssh
70 :``pull``: changesets received via ``hg pull``
70 :``pull``: changesets received via ``hg pull``
71 :``unbundle``: changesets received via ``hg unbundle``
71 :``unbundle``: changesets received via ``hg unbundle``
72 :``push``: changesets sent or received via ``hg push``
72 :``push``: changesets sent or received via ``hg push``
73 :``bundle``: changesets sent via ``hg unbundle``
73 :``bundle``: changesets sent via ``hg unbundle``
74
74
75 Default: serve.
75 Default: serve.
76
76
77 notify.strip
77 notify.strip
78 Number of leading slashes to strip from url paths. By default, notifications
78 Number of leading slashes to strip from url paths. By default, notifications
79 reference repositories with their absolute path. ``notify.strip`` lets you
79 reference repositories with their absolute path. ``notify.strip`` lets you
80 turn them into relative paths. For example, ``notify.strip=3`` will change
80 turn them into relative paths. For example, ``notify.strip=3`` will change
81 ``/long/path/repository`` into ``repository``. Default: 0.
81 ``/long/path/repository`` into ``repository``. Default: 0.
82
82
83 notify.domain
83 notify.domain
84 Default email domain for sender or recipients with no explicit domain.
84 Default email domain for sender or recipients with no explicit domain.
85
85
86 notify.style
86 notify.style
87 Style file to use when formatting emails.
87 Style file to use when formatting emails.
88
88
89 notify.template
89 notify.template
90 Template to use when formatting emails.
90 Template to use when formatting emails.
91
91
92 notify.incoming
92 notify.incoming
93 Template to use when run as an incoming hook, overriding ``notify.template``.
93 Template to use when run as an incoming hook, overriding ``notify.template``.
94
94
95 notify.outgoing
95 notify.outgoing
96 Template to use when run as an outgoing hook, overriding ``notify.template``.
96 Template to use when run as an outgoing hook, overriding ``notify.template``.
97
97
98 notify.changegroup
98 notify.changegroup
99 Template to use when running as a changegroup hook, overriding
99 Template to use when running as a changegroup hook, overriding
100 ``notify.template``.
100 ``notify.template``.
101
101
102 notify.maxdiff
102 notify.maxdiff
103 Maximum number of diff lines to include in notification email. Set to 0
103 Maximum number of diff lines to include in notification email. Set to 0
104 to disable the diff, or -1 to include all of it. Default: 300.
104 to disable the diff, or -1 to include all of it. Default: 300.
105
105
106 notify.maxsubject
106 notify.maxsubject
107 Maximum number of characters in email's subject line. Default: 67.
107 Maximum number of characters in email's subject line. Default: 67.
108
108
109 notify.diffstat
109 notify.diffstat
110 Set to True to include a diffstat before diff content. Default: True.
110 Set to True to include a diffstat before diff content. Default: True.
111
111
112 notify.merge
112 notify.merge
113 If True, send notifications for merge changesets. Default: True.
113 If True, send notifications for merge changesets. Default: True.
114
114
115 notify.mbox
115 notify.mbox
116 If set, append mails to this mbox file instead of sending. Default: None.
116 If set, append mails to this mbox file instead of sending. Default: None.
117
117
118 notify.fromauthor
118 notify.fromauthor
119 If set, use the committer of the first changeset in a changegroup for
119 If set, use the committer of the first changeset in a changegroup for
120 the "From" field of the notification mail. If not set, take the user
120 the "From" field of the notification mail. If not set, take the user
121 from the pushing repo. Default: False.
121 from the pushing repo. Default: False.
122
122
123 If set, the following entries will also be used to customize the
123 If set, the following entries will also be used to customize the
124 notifications:
124 notifications:
125
125
126 email.from
126 email.from
127 Email ``From`` address to use if none can be found in the generated
127 Email ``From`` address to use if none can be found in the generated
128 email content.
128 email content.
129
129
130 web.baseurl
130 web.baseurl
131 Root repository URL to combine with repository paths when making
131 Root repository URL to combine with repository paths when making
132 references. See also ``notify.strip``.
132 references. See also ``notify.strip``.
133
133
134 '''
134 '''
135 from __future__ import absolute_import
135 from __future__ import absolute_import
136
136
137 import email
137 import email
138 import fnmatch
138 import fnmatch
139 import socket
139 import socket
140 import time
140 import time
141
141
142 from mercurial.i18n import _
142 from mercurial.i18n import _
143 from mercurial import (
143 from mercurial import (
144 cmdutil,
144 cmdutil,
145 error,
145 error,
146 mail,
146 mail,
147 patch,
147 patch,
148 registrar,
148 registrar,
149 util,
149 util,
150 )
150 )
151
151
152 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
152 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
153 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
153 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
154 # be specifying the version(s) of Mercurial they are tested with, or
154 # be specifying the version(s) of Mercurial they are tested with, or
155 # leave the attribute unspecified.
155 # leave the attribute unspecified.
156 testedwith = 'ships-with-hg-core'
156 testedwith = 'ships-with-hg-core'
157
157
158 configtable = {}
158 configtable = {}
159 configitem = registrar.configitem(configtable)
159 configitem = registrar.configitem(configtable)
160
160
161 configitem('notify', 'config',
161 configitem('notify', 'config',
162 default=None,
162 default=None,
163 )
163 )
164 configitem('notify', 'diffstat',
164 configitem('notify', 'diffstat',
165 default=True,
165 default=True,
166 )
166 )
167 configitem('notify', 'domain',
167 configitem('notify', 'domain',
168 default=None,
168 default=None,
169 )
169 )
170 configitem('notify', 'fromauthor',
170 configitem('notify', 'fromauthor',
171 default=None,
171 default=None,
172 )
172 )
173 configitem('notify', 'maxdiff',
173 configitem('notify', 'maxdiff',
174 default=300,
174 default=300,
175 )
175 )
176 configitem('notify', 'maxsubject',
176 configitem('notify', 'maxsubject',
177 default=67,
177 default=67,
178 )
178 )
179 configitem('notify', 'mbox',
179 configitem('notify', 'mbox',
180 default=None,
180 default=None,
181 )
181 )
182 configitem('notify', 'merge',
182 configitem('notify', 'merge',
183 default=True,
183 default=True,
184 )
184 )
185 configitem('notify', 'sources',
185 configitem('notify', 'sources',
186 default='serve',
186 default='serve',
187 )
187 )
188 configitem('notify', 'strip',
188 configitem('notify', 'strip',
189 default=0,
189 default=0,
190 )
190 )
191 configitem('notify', 'style',
191 configitem('notify', 'style',
192 default=None,
192 default=None,
193 )
193 )
194 configitem('notify', 'template',
195 default=None,
196 )
194
197
195 # template for single changeset can include email headers.
198 # template for single changeset can include email headers.
196 single_template = '''
199 single_template = '''
197 Subject: changeset in {webroot}: {desc|firstline|strip}
200 Subject: changeset in {webroot}: {desc|firstline|strip}
198 From: {author}
201 From: {author}
199
202
200 changeset {node|short} in {root}
203 changeset {node|short} in {root}
201 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
204 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
202 description:
205 description:
203 \t{desc|tabindent|strip}
206 \t{desc|tabindent|strip}
204 '''.lstrip()
207 '''.lstrip()
205
208
206 # template for multiple changesets should not contain email headers,
209 # template for multiple changesets should not contain email headers,
207 # because only first set of headers will be used and result will look
210 # because only first set of headers will be used and result will look
208 # strange.
211 # strange.
209 multiple_template = '''
212 multiple_template = '''
210 changeset {node|short} in {root}
213 changeset {node|short} in {root}
211 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
214 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
212 summary: {desc|firstline}
215 summary: {desc|firstline}
213 '''
216 '''
214
217
215 deftemplates = {
218 deftemplates = {
216 'changegroup': multiple_template,
219 'changegroup': multiple_template,
217 }
220 }
218
221
219 class notifier(object):
222 class notifier(object):
220 '''email notification class.'''
223 '''email notification class.'''
221
224
222 def __init__(self, ui, repo, hooktype):
225 def __init__(self, ui, repo, hooktype):
223 self.ui = ui
226 self.ui = ui
224 cfg = self.ui.config('notify', 'config')
227 cfg = self.ui.config('notify', 'config')
225 if cfg:
228 if cfg:
226 self.ui.readconfig(cfg, sections=['usersubs', 'reposubs'])
229 self.ui.readconfig(cfg, sections=['usersubs', 'reposubs'])
227 self.repo = repo
230 self.repo = repo
228 self.stripcount = int(self.ui.config('notify', 'strip'))
231 self.stripcount = int(self.ui.config('notify', 'strip'))
229 self.root = self.strip(self.repo.root)
232 self.root = self.strip(self.repo.root)
230 self.domain = self.ui.config('notify', 'domain')
233 self.domain = self.ui.config('notify', 'domain')
231 self.mbox = self.ui.config('notify', 'mbox')
234 self.mbox = self.ui.config('notify', 'mbox')
232 self.test = self.ui.configbool('notify', 'test', True)
235 self.test = self.ui.configbool('notify', 'test', True)
233 self.charsets = mail._charsets(self.ui)
236 self.charsets = mail._charsets(self.ui)
234 self.subs = self.subscribers()
237 self.subs = self.subscribers()
235 self.merge = self.ui.configbool('notify', 'merge')
238 self.merge = self.ui.configbool('notify', 'merge')
236
239
237 mapfile = None
240 mapfile = None
238 template = (self.ui.config('notify', hooktype) or
241 template = (self.ui.config('notify', hooktype) or
239 self.ui.config('notify', 'template'))
242 self.ui.config('notify', 'template'))
240 if not template:
243 if not template:
241 mapfile = self.ui.config('notify', 'style')
244 mapfile = self.ui.config('notify', 'style')
242 if not mapfile and not template:
245 if not mapfile and not template:
243 template = deftemplates.get(hooktype) or single_template
246 template = deftemplates.get(hooktype) or single_template
244 spec = cmdutil.logtemplatespec(template, mapfile)
247 spec = cmdutil.logtemplatespec(template, mapfile)
245 self.t = cmdutil.changeset_templater(self.ui, self.repo, spec,
248 self.t = cmdutil.changeset_templater(self.ui, self.repo, spec,
246 False, None, False)
249 False, None, False)
247
250
248 def strip(self, path):
251 def strip(self, path):
249 '''strip leading slashes from local path, turn into web-safe path.'''
252 '''strip leading slashes from local path, turn into web-safe path.'''
250
253
251 path = util.pconvert(path)
254 path = util.pconvert(path)
252 count = self.stripcount
255 count = self.stripcount
253 while count > 0:
256 while count > 0:
254 c = path.find('/')
257 c = path.find('/')
255 if c == -1:
258 if c == -1:
256 break
259 break
257 path = path[c + 1:]
260 path = path[c + 1:]
258 count -= 1
261 count -= 1
259 return path
262 return path
260
263
261 def fixmail(self, addr):
264 def fixmail(self, addr):
262 '''try to clean up email addresses.'''
265 '''try to clean up email addresses.'''
263
266
264 addr = util.email(addr.strip())
267 addr = util.email(addr.strip())
265 if self.domain:
268 if self.domain:
266 a = addr.find('@localhost')
269 a = addr.find('@localhost')
267 if a != -1:
270 if a != -1:
268 addr = addr[:a]
271 addr = addr[:a]
269 if '@' not in addr:
272 if '@' not in addr:
270 return addr + '@' + self.domain
273 return addr + '@' + self.domain
271 return addr
274 return addr
272
275
273 def subscribers(self):
276 def subscribers(self):
274 '''return list of email addresses of subscribers to this repo.'''
277 '''return list of email addresses of subscribers to this repo.'''
275 subs = set()
278 subs = set()
276 for user, pats in self.ui.configitems('usersubs'):
279 for user, pats in self.ui.configitems('usersubs'):
277 for pat in pats.split(','):
280 for pat in pats.split(','):
278 if '#' in pat:
281 if '#' in pat:
279 pat, revs = pat.split('#', 1)
282 pat, revs = pat.split('#', 1)
280 else:
283 else:
281 revs = None
284 revs = None
282 if fnmatch.fnmatch(self.repo.root, pat.strip()):
285 if fnmatch.fnmatch(self.repo.root, pat.strip()):
283 subs.add((self.fixmail(user), revs))
286 subs.add((self.fixmail(user), revs))
284 for pat, users in self.ui.configitems('reposubs'):
287 for pat, users in self.ui.configitems('reposubs'):
285 if '#' in pat:
288 if '#' in pat:
286 pat, revs = pat.split('#', 1)
289 pat, revs = pat.split('#', 1)
287 else:
290 else:
288 revs = None
291 revs = None
289 if fnmatch.fnmatch(self.repo.root, pat):
292 if fnmatch.fnmatch(self.repo.root, pat):
290 for user in users.split(','):
293 for user in users.split(','):
291 subs.add((self.fixmail(user), revs))
294 subs.add((self.fixmail(user), revs))
292 return [(mail.addressencode(self.ui, s, self.charsets, self.test), r)
295 return [(mail.addressencode(self.ui, s, self.charsets, self.test), r)
293 for s, r in sorted(subs)]
296 for s, r in sorted(subs)]
294
297
295 def node(self, ctx, **props):
298 def node(self, ctx, **props):
296 '''format one changeset, unless it is a suppressed merge.'''
299 '''format one changeset, unless it is a suppressed merge.'''
297 if not self.merge and len(ctx.parents()) > 1:
300 if not self.merge and len(ctx.parents()) > 1:
298 return False
301 return False
299 self.t.show(ctx, changes=ctx.changeset(),
302 self.t.show(ctx, changes=ctx.changeset(),
300 baseurl=self.ui.config('web', 'baseurl'),
303 baseurl=self.ui.config('web', 'baseurl'),
301 root=self.repo.root, webroot=self.root, **props)
304 root=self.repo.root, webroot=self.root, **props)
302 return True
305 return True
303
306
304 def skipsource(self, source):
307 def skipsource(self, source):
305 '''true if incoming changes from this source should be skipped.'''
308 '''true if incoming changes from this source should be skipped.'''
306 ok_sources = self.ui.config('notify', 'sources').split()
309 ok_sources = self.ui.config('notify', 'sources').split()
307 return source not in ok_sources
310 return source not in ok_sources
308
311
309 def send(self, ctx, count, data):
312 def send(self, ctx, count, data):
310 '''send message.'''
313 '''send message.'''
311
314
312 # Select subscribers by revset
315 # Select subscribers by revset
313 subs = set()
316 subs = set()
314 for sub, spec in self.subs:
317 for sub, spec in self.subs:
315 if spec is None:
318 if spec is None:
316 subs.add(sub)
319 subs.add(sub)
317 continue
320 continue
318 revs = self.repo.revs('%r and %d:', spec, ctx.rev())
321 revs = self.repo.revs('%r and %d:', spec, ctx.rev())
319 if len(revs):
322 if len(revs):
320 subs.add(sub)
323 subs.add(sub)
321 continue
324 continue
322 if len(subs) == 0:
325 if len(subs) == 0:
323 self.ui.debug('notify: no subscribers to selected repo '
326 self.ui.debug('notify: no subscribers to selected repo '
324 'and revset\n')
327 'and revset\n')
325 return
328 return
326
329
327 p = email.Parser.Parser()
330 p = email.Parser.Parser()
328 try:
331 try:
329 msg = p.parsestr(data)
332 msg = p.parsestr(data)
330 except email.Errors.MessageParseError as inst:
333 except email.Errors.MessageParseError as inst:
331 raise error.Abort(inst)
334 raise error.Abort(inst)
332
335
333 # store sender and subject
336 # store sender and subject
334 sender, subject = msg['From'], msg['Subject']
337 sender, subject = msg['From'], msg['Subject']
335 del msg['From'], msg['Subject']
338 del msg['From'], msg['Subject']
336
339
337 if not msg.is_multipart():
340 if not msg.is_multipart():
338 # create fresh mime message from scratch
341 # create fresh mime message from scratch
339 # (multipart templates must take care of this themselves)
342 # (multipart templates must take care of this themselves)
340 headers = msg.items()
343 headers = msg.items()
341 payload = msg.get_payload()
344 payload = msg.get_payload()
342 # for notification prefer readability over data precision
345 # for notification prefer readability over data precision
343 msg = mail.mimeencode(self.ui, payload, self.charsets, self.test)
346 msg = mail.mimeencode(self.ui, payload, self.charsets, self.test)
344 # reinstate custom headers
347 # reinstate custom headers
345 for k, v in headers:
348 for k, v in headers:
346 msg[k] = v
349 msg[k] = v
347
350
348 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
351 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
349
352
350 # try to make subject line exist and be useful
353 # try to make subject line exist and be useful
351 if not subject:
354 if not subject:
352 if count > 1:
355 if count > 1:
353 subject = _('%s: %d new changesets') % (self.root, count)
356 subject = _('%s: %d new changesets') % (self.root, count)
354 else:
357 else:
355 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
358 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
356 subject = '%s: %s' % (self.root, s)
359 subject = '%s: %s' % (self.root, s)
357 maxsubject = int(self.ui.config('notify', 'maxsubject'))
360 maxsubject = int(self.ui.config('notify', 'maxsubject'))
358 if maxsubject:
361 if maxsubject:
359 subject = util.ellipsis(subject, maxsubject)
362 subject = util.ellipsis(subject, maxsubject)
360 msg['Subject'] = mail.headencode(self.ui, subject,
363 msg['Subject'] = mail.headencode(self.ui, subject,
361 self.charsets, self.test)
364 self.charsets, self.test)
362
365
363 # try to make message have proper sender
366 # try to make message have proper sender
364 if not sender:
367 if not sender:
365 sender = self.ui.config('email', 'from') or self.ui.username()
368 sender = self.ui.config('email', 'from') or self.ui.username()
366 if '@' not in sender or '@localhost' in sender:
369 if '@' not in sender or '@localhost' in sender:
367 sender = self.fixmail(sender)
370 sender = self.fixmail(sender)
368 msg['From'] = mail.addressencode(self.ui, sender,
371 msg['From'] = mail.addressencode(self.ui, sender,
369 self.charsets, self.test)
372 self.charsets, self.test)
370
373
371 msg['X-Hg-Notification'] = 'changeset %s' % ctx
374 msg['X-Hg-Notification'] = 'changeset %s' % ctx
372 if not msg['Message-Id']:
375 if not msg['Message-Id']:
373 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
376 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
374 (ctx, int(time.time()),
377 (ctx, int(time.time()),
375 hash(self.repo.root), socket.getfqdn()))
378 hash(self.repo.root), socket.getfqdn()))
376 msg['To'] = ', '.join(sorted(subs))
379 msg['To'] = ', '.join(sorted(subs))
377
380
378 msgtext = msg.as_string()
381 msgtext = msg.as_string()
379 if self.test:
382 if self.test:
380 self.ui.write(msgtext)
383 self.ui.write(msgtext)
381 if not msgtext.endswith('\n'):
384 if not msgtext.endswith('\n'):
382 self.ui.write('\n')
385 self.ui.write('\n')
383 else:
386 else:
384 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
387 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
385 (len(subs), count))
388 (len(subs), count))
386 mail.sendmail(self.ui, util.email(msg['From']),
389 mail.sendmail(self.ui, util.email(msg['From']),
387 subs, msgtext, mbox=self.mbox)
390 subs, msgtext, mbox=self.mbox)
388
391
389 def diff(self, ctx, ref=None):
392 def diff(self, ctx, ref=None):
390
393
391 maxdiff = int(self.ui.config('notify', 'maxdiff'))
394 maxdiff = int(self.ui.config('notify', 'maxdiff'))
392 prev = ctx.p1().node()
395 prev = ctx.p1().node()
393 if ref:
396 if ref:
394 ref = ref.node()
397 ref = ref.node()
395 else:
398 else:
396 ref = ctx.node()
399 ref = ctx.node()
397 chunks = patch.diff(self.repo, prev, ref,
400 chunks = patch.diff(self.repo, prev, ref,
398 opts=patch.diffallopts(self.ui))
401 opts=patch.diffallopts(self.ui))
399 difflines = ''.join(chunks).splitlines()
402 difflines = ''.join(chunks).splitlines()
400
403
401 if self.ui.configbool('notify', 'diffstat'):
404 if self.ui.configbool('notify', 'diffstat'):
402 s = patch.diffstat(difflines)
405 s = patch.diffstat(difflines)
403 # s may be nil, don't include the header if it is
406 # s may be nil, don't include the header if it is
404 if s:
407 if s:
405 self.ui.write(_('\ndiffstat:\n\n%s') % s)
408 self.ui.write(_('\ndiffstat:\n\n%s') % s)
406
409
407 if maxdiff == 0:
410 if maxdiff == 0:
408 return
411 return
409 elif maxdiff > 0 and len(difflines) > maxdiff:
412 elif maxdiff > 0 and len(difflines) > maxdiff:
410 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
413 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
411 self.ui.write(msg % (len(difflines), maxdiff))
414 self.ui.write(msg % (len(difflines), maxdiff))
412 difflines = difflines[:maxdiff]
415 difflines = difflines[:maxdiff]
413 elif difflines:
416 elif difflines:
414 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
417 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
415
418
416 self.ui.write("\n".join(difflines))
419 self.ui.write("\n".join(difflines))
417
420
418 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
421 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
419 '''send email notifications to interested subscribers.
422 '''send email notifications to interested subscribers.
420
423
421 if used as changegroup hook, send one email for all changesets in
424 if used as changegroup hook, send one email for all changesets in
422 changegroup. else send one email per changeset.'''
425 changegroup. else send one email per changeset.'''
423
426
424 n = notifier(ui, repo, hooktype)
427 n = notifier(ui, repo, hooktype)
425 ctx = repo[node]
428 ctx = repo[node]
426
429
427 if not n.subs:
430 if not n.subs:
428 ui.debug('notify: no subscribers to repository %s\n' % n.root)
431 ui.debug('notify: no subscribers to repository %s\n' % n.root)
429 return
432 return
430 if n.skipsource(source):
433 if n.skipsource(source):
431 ui.debug('notify: changes have source "%s" - skipping\n' % source)
434 ui.debug('notify: changes have source "%s" - skipping\n' % source)
432 return
435 return
433
436
434 ui.pushbuffer()
437 ui.pushbuffer()
435 data = ''
438 data = ''
436 count = 0
439 count = 0
437 author = ''
440 author = ''
438 if hooktype == 'changegroup' or hooktype == 'outgoing':
441 if hooktype == 'changegroup' or hooktype == 'outgoing':
439 start, end = ctx.rev(), len(repo)
442 start, end = ctx.rev(), len(repo)
440 for rev in xrange(start, end):
443 for rev in xrange(start, end):
441 if n.node(repo[rev]):
444 if n.node(repo[rev]):
442 count += 1
445 count += 1
443 if not author:
446 if not author:
444 author = repo[rev].user()
447 author = repo[rev].user()
445 else:
448 else:
446 data += ui.popbuffer()
449 data += ui.popbuffer()
447 ui.note(_('notify: suppressing notification for merge %d:%s\n')
450 ui.note(_('notify: suppressing notification for merge %d:%s\n')
448 % (rev, repo[rev].hex()[:12]))
451 % (rev, repo[rev].hex()[:12]))
449 ui.pushbuffer()
452 ui.pushbuffer()
450 if count:
453 if count:
451 n.diff(ctx, repo['tip'])
454 n.diff(ctx, repo['tip'])
452 else:
455 else:
453 if not n.node(ctx):
456 if not n.node(ctx):
454 ui.popbuffer()
457 ui.popbuffer()
455 ui.note(_('notify: suppressing notification for merge %d:%s\n') %
458 ui.note(_('notify: suppressing notification for merge %d:%s\n') %
456 (ctx.rev(), ctx.hex()[:12]))
459 (ctx.rev(), ctx.hex()[:12]))
457 return
460 return
458 count += 1
461 count += 1
459 n.diff(ctx)
462 n.diff(ctx)
460 if not author:
463 if not author:
461 author = ctx.user()
464 author = ctx.user()
462
465
463 data += ui.popbuffer()
466 data += ui.popbuffer()
464 fromauthor = ui.config('notify', 'fromauthor')
467 fromauthor = ui.config('notify', 'fromauthor')
465 if author and fromauthor:
468 if author and fromauthor:
466 data = '\n'.join(['From: %s' % author, data])
469 data = '\n'.join(['From: %s' % author, data])
467
470
468 if count:
471 if count:
469 n.send(ctx, count, data)
472 n.send(ctx, count, data)
General Comments 0
You need to be logged in to leave comments. Login now