##// END OF EJS Templates
configitems: register the 'notify.test' config
Boris Feld -
r33750:a0e28577 default
parent child Browse files
Show More
@@ -1,472 +1,475
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',
194 configitem('notify', 'template',
195 default=None,
195 default=None,
196 )
196 )
197 configitem('notify', 'test',
198 default=True,
199 )
197
200
198 # template for single changeset can include email headers.
201 # template for single changeset can include email headers.
199 single_template = '''
202 single_template = '''
200 Subject: changeset in {webroot}: {desc|firstline|strip}
203 Subject: changeset in {webroot}: {desc|firstline|strip}
201 From: {author}
204 From: {author}
202
205
203 changeset {node|short} in {root}
206 changeset {node|short} in {root}
204 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
207 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
205 description:
208 description:
206 \t{desc|tabindent|strip}
209 \t{desc|tabindent|strip}
207 '''.lstrip()
210 '''.lstrip()
208
211
209 # template for multiple changesets should not contain email headers,
212 # template for multiple changesets should not contain email headers,
210 # because only first set of headers will be used and result will look
213 # because only first set of headers will be used and result will look
211 # strange.
214 # strange.
212 multiple_template = '''
215 multiple_template = '''
213 changeset {node|short} in {root}
216 changeset {node|short} in {root}
214 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
217 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
215 summary: {desc|firstline}
218 summary: {desc|firstline}
216 '''
219 '''
217
220
218 deftemplates = {
221 deftemplates = {
219 'changegroup': multiple_template,
222 'changegroup': multiple_template,
220 }
223 }
221
224
222 class notifier(object):
225 class notifier(object):
223 '''email notification class.'''
226 '''email notification class.'''
224
227
225 def __init__(self, ui, repo, hooktype):
228 def __init__(self, ui, repo, hooktype):
226 self.ui = ui
229 self.ui = ui
227 cfg = self.ui.config('notify', 'config')
230 cfg = self.ui.config('notify', 'config')
228 if cfg:
231 if cfg:
229 self.ui.readconfig(cfg, sections=['usersubs', 'reposubs'])
232 self.ui.readconfig(cfg, sections=['usersubs', 'reposubs'])
230 self.repo = repo
233 self.repo = repo
231 self.stripcount = int(self.ui.config('notify', 'strip'))
234 self.stripcount = int(self.ui.config('notify', 'strip'))
232 self.root = self.strip(self.repo.root)
235 self.root = self.strip(self.repo.root)
233 self.domain = self.ui.config('notify', 'domain')
236 self.domain = self.ui.config('notify', 'domain')
234 self.mbox = self.ui.config('notify', 'mbox')
237 self.mbox = self.ui.config('notify', 'mbox')
235 self.test = self.ui.configbool('notify', 'test', True)
238 self.test = self.ui.configbool('notify', 'test')
236 self.charsets = mail._charsets(self.ui)
239 self.charsets = mail._charsets(self.ui)
237 self.subs = self.subscribers()
240 self.subs = self.subscribers()
238 self.merge = self.ui.configbool('notify', 'merge')
241 self.merge = self.ui.configbool('notify', 'merge')
239
242
240 mapfile = None
243 mapfile = None
241 template = (self.ui.config('notify', hooktype) or
244 template = (self.ui.config('notify', hooktype) or
242 self.ui.config('notify', 'template'))
245 self.ui.config('notify', 'template'))
243 if not template:
246 if not template:
244 mapfile = self.ui.config('notify', 'style')
247 mapfile = self.ui.config('notify', 'style')
245 if not mapfile and not template:
248 if not mapfile and not template:
246 template = deftemplates.get(hooktype) or single_template
249 template = deftemplates.get(hooktype) or single_template
247 spec = cmdutil.logtemplatespec(template, mapfile)
250 spec = cmdutil.logtemplatespec(template, mapfile)
248 self.t = cmdutil.changeset_templater(self.ui, self.repo, spec,
251 self.t = cmdutil.changeset_templater(self.ui, self.repo, spec,
249 False, None, False)
252 False, None, False)
250
253
251 def strip(self, path):
254 def strip(self, path):
252 '''strip leading slashes from local path, turn into web-safe path.'''
255 '''strip leading slashes from local path, turn into web-safe path.'''
253
256
254 path = util.pconvert(path)
257 path = util.pconvert(path)
255 count = self.stripcount
258 count = self.stripcount
256 while count > 0:
259 while count > 0:
257 c = path.find('/')
260 c = path.find('/')
258 if c == -1:
261 if c == -1:
259 break
262 break
260 path = path[c + 1:]
263 path = path[c + 1:]
261 count -= 1
264 count -= 1
262 return path
265 return path
263
266
264 def fixmail(self, addr):
267 def fixmail(self, addr):
265 '''try to clean up email addresses.'''
268 '''try to clean up email addresses.'''
266
269
267 addr = util.email(addr.strip())
270 addr = util.email(addr.strip())
268 if self.domain:
271 if self.domain:
269 a = addr.find('@localhost')
272 a = addr.find('@localhost')
270 if a != -1:
273 if a != -1:
271 addr = addr[:a]
274 addr = addr[:a]
272 if '@' not in addr:
275 if '@' not in addr:
273 return addr + '@' + self.domain
276 return addr + '@' + self.domain
274 return addr
277 return addr
275
278
276 def subscribers(self):
279 def subscribers(self):
277 '''return list of email addresses of subscribers to this repo.'''
280 '''return list of email addresses of subscribers to this repo.'''
278 subs = set()
281 subs = set()
279 for user, pats in self.ui.configitems('usersubs'):
282 for user, pats in self.ui.configitems('usersubs'):
280 for pat in pats.split(','):
283 for pat in pats.split(','):
281 if '#' in pat:
284 if '#' in pat:
282 pat, revs = pat.split('#', 1)
285 pat, revs = pat.split('#', 1)
283 else:
286 else:
284 revs = None
287 revs = None
285 if fnmatch.fnmatch(self.repo.root, pat.strip()):
288 if fnmatch.fnmatch(self.repo.root, pat.strip()):
286 subs.add((self.fixmail(user), revs))
289 subs.add((self.fixmail(user), revs))
287 for pat, users in self.ui.configitems('reposubs'):
290 for pat, users in self.ui.configitems('reposubs'):
288 if '#' in pat:
291 if '#' in pat:
289 pat, revs = pat.split('#', 1)
292 pat, revs = pat.split('#', 1)
290 else:
293 else:
291 revs = None
294 revs = None
292 if fnmatch.fnmatch(self.repo.root, pat):
295 if fnmatch.fnmatch(self.repo.root, pat):
293 for user in users.split(','):
296 for user in users.split(','):
294 subs.add((self.fixmail(user), revs))
297 subs.add((self.fixmail(user), revs))
295 return [(mail.addressencode(self.ui, s, self.charsets, self.test), r)
298 return [(mail.addressencode(self.ui, s, self.charsets, self.test), r)
296 for s, r in sorted(subs)]
299 for s, r in sorted(subs)]
297
300
298 def node(self, ctx, **props):
301 def node(self, ctx, **props):
299 '''format one changeset, unless it is a suppressed merge.'''
302 '''format one changeset, unless it is a suppressed merge.'''
300 if not self.merge and len(ctx.parents()) > 1:
303 if not self.merge and len(ctx.parents()) > 1:
301 return False
304 return False
302 self.t.show(ctx, changes=ctx.changeset(),
305 self.t.show(ctx, changes=ctx.changeset(),
303 baseurl=self.ui.config('web', 'baseurl'),
306 baseurl=self.ui.config('web', 'baseurl'),
304 root=self.repo.root, webroot=self.root, **props)
307 root=self.repo.root, webroot=self.root, **props)
305 return True
308 return True
306
309
307 def skipsource(self, source):
310 def skipsource(self, source):
308 '''true if incoming changes from this source should be skipped.'''
311 '''true if incoming changes from this source should be skipped.'''
309 ok_sources = self.ui.config('notify', 'sources').split()
312 ok_sources = self.ui.config('notify', 'sources').split()
310 return source not in ok_sources
313 return source not in ok_sources
311
314
312 def send(self, ctx, count, data):
315 def send(self, ctx, count, data):
313 '''send message.'''
316 '''send message.'''
314
317
315 # Select subscribers by revset
318 # Select subscribers by revset
316 subs = set()
319 subs = set()
317 for sub, spec in self.subs:
320 for sub, spec in self.subs:
318 if spec is None:
321 if spec is None:
319 subs.add(sub)
322 subs.add(sub)
320 continue
323 continue
321 revs = self.repo.revs('%r and %d:', spec, ctx.rev())
324 revs = self.repo.revs('%r and %d:', spec, ctx.rev())
322 if len(revs):
325 if len(revs):
323 subs.add(sub)
326 subs.add(sub)
324 continue
327 continue
325 if len(subs) == 0:
328 if len(subs) == 0:
326 self.ui.debug('notify: no subscribers to selected repo '
329 self.ui.debug('notify: no subscribers to selected repo '
327 'and revset\n')
330 'and revset\n')
328 return
331 return
329
332
330 p = email.Parser.Parser()
333 p = email.Parser.Parser()
331 try:
334 try:
332 msg = p.parsestr(data)
335 msg = p.parsestr(data)
333 except email.Errors.MessageParseError as inst:
336 except email.Errors.MessageParseError as inst:
334 raise error.Abort(inst)
337 raise error.Abort(inst)
335
338
336 # store sender and subject
339 # store sender and subject
337 sender, subject = msg['From'], msg['Subject']
340 sender, subject = msg['From'], msg['Subject']
338 del msg['From'], msg['Subject']
341 del msg['From'], msg['Subject']
339
342
340 if not msg.is_multipart():
343 if not msg.is_multipart():
341 # create fresh mime message from scratch
344 # create fresh mime message from scratch
342 # (multipart templates must take care of this themselves)
345 # (multipart templates must take care of this themselves)
343 headers = msg.items()
346 headers = msg.items()
344 payload = msg.get_payload()
347 payload = msg.get_payload()
345 # for notification prefer readability over data precision
348 # for notification prefer readability over data precision
346 msg = mail.mimeencode(self.ui, payload, self.charsets, self.test)
349 msg = mail.mimeencode(self.ui, payload, self.charsets, self.test)
347 # reinstate custom headers
350 # reinstate custom headers
348 for k, v in headers:
351 for k, v in headers:
349 msg[k] = v
352 msg[k] = v
350
353
351 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
354 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
352
355
353 # try to make subject line exist and be useful
356 # try to make subject line exist and be useful
354 if not subject:
357 if not subject:
355 if count > 1:
358 if count > 1:
356 subject = _('%s: %d new changesets') % (self.root, count)
359 subject = _('%s: %d new changesets') % (self.root, count)
357 else:
360 else:
358 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
361 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
359 subject = '%s: %s' % (self.root, s)
362 subject = '%s: %s' % (self.root, s)
360 maxsubject = int(self.ui.config('notify', 'maxsubject'))
363 maxsubject = int(self.ui.config('notify', 'maxsubject'))
361 if maxsubject:
364 if maxsubject:
362 subject = util.ellipsis(subject, maxsubject)
365 subject = util.ellipsis(subject, maxsubject)
363 msg['Subject'] = mail.headencode(self.ui, subject,
366 msg['Subject'] = mail.headencode(self.ui, subject,
364 self.charsets, self.test)
367 self.charsets, self.test)
365
368
366 # try to make message have proper sender
369 # try to make message have proper sender
367 if not sender:
370 if not sender:
368 sender = self.ui.config('email', 'from') or self.ui.username()
371 sender = self.ui.config('email', 'from') or self.ui.username()
369 if '@' not in sender or '@localhost' in sender:
372 if '@' not in sender or '@localhost' in sender:
370 sender = self.fixmail(sender)
373 sender = self.fixmail(sender)
371 msg['From'] = mail.addressencode(self.ui, sender,
374 msg['From'] = mail.addressencode(self.ui, sender,
372 self.charsets, self.test)
375 self.charsets, self.test)
373
376
374 msg['X-Hg-Notification'] = 'changeset %s' % ctx
377 msg['X-Hg-Notification'] = 'changeset %s' % ctx
375 if not msg['Message-Id']:
378 if not msg['Message-Id']:
376 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
379 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
377 (ctx, int(time.time()),
380 (ctx, int(time.time()),
378 hash(self.repo.root), socket.getfqdn()))
381 hash(self.repo.root), socket.getfqdn()))
379 msg['To'] = ', '.join(sorted(subs))
382 msg['To'] = ', '.join(sorted(subs))
380
383
381 msgtext = msg.as_string()
384 msgtext = msg.as_string()
382 if self.test:
385 if self.test:
383 self.ui.write(msgtext)
386 self.ui.write(msgtext)
384 if not msgtext.endswith('\n'):
387 if not msgtext.endswith('\n'):
385 self.ui.write('\n')
388 self.ui.write('\n')
386 else:
389 else:
387 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
390 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
388 (len(subs), count))
391 (len(subs), count))
389 mail.sendmail(self.ui, util.email(msg['From']),
392 mail.sendmail(self.ui, util.email(msg['From']),
390 subs, msgtext, mbox=self.mbox)
393 subs, msgtext, mbox=self.mbox)
391
394
392 def diff(self, ctx, ref=None):
395 def diff(self, ctx, ref=None):
393
396
394 maxdiff = int(self.ui.config('notify', 'maxdiff'))
397 maxdiff = int(self.ui.config('notify', 'maxdiff'))
395 prev = ctx.p1().node()
398 prev = ctx.p1().node()
396 if ref:
399 if ref:
397 ref = ref.node()
400 ref = ref.node()
398 else:
401 else:
399 ref = ctx.node()
402 ref = ctx.node()
400 chunks = patch.diff(self.repo, prev, ref,
403 chunks = patch.diff(self.repo, prev, ref,
401 opts=patch.diffallopts(self.ui))
404 opts=patch.diffallopts(self.ui))
402 difflines = ''.join(chunks).splitlines()
405 difflines = ''.join(chunks).splitlines()
403
406
404 if self.ui.configbool('notify', 'diffstat'):
407 if self.ui.configbool('notify', 'diffstat'):
405 s = patch.diffstat(difflines)
408 s = patch.diffstat(difflines)
406 # s may be nil, don't include the header if it is
409 # s may be nil, don't include the header if it is
407 if s:
410 if s:
408 self.ui.write(_('\ndiffstat:\n\n%s') % s)
411 self.ui.write(_('\ndiffstat:\n\n%s') % s)
409
412
410 if maxdiff == 0:
413 if maxdiff == 0:
411 return
414 return
412 elif maxdiff > 0 and len(difflines) > maxdiff:
415 elif maxdiff > 0 and len(difflines) > maxdiff:
413 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
416 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
414 self.ui.write(msg % (len(difflines), maxdiff))
417 self.ui.write(msg % (len(difflines), maxdiff))
415 difflines = difflines[:maxdiff]
418 difflines = difflines[:maxdiff]
416 elif difflines:
419 elif difflines:
417 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
420 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
418
421
419 self.ui.write("\n".join(difflines))
422 self.ui.write("\n".join(difflines))
420
423
421 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
424 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
422 '''send email notifications to interested subscribers.
425 '''send email notifications to interested subscribers.
423
426
424 if used as changegroup hook, send one email for all changesets in
427 if used as changegroup hook, send one email for all changesets in
425 changegroup. else send one email per changeset.'''
428 changegroup. else send one email per changeset.'''
426
429
427 n = notifier(ui, repo, hooktype)
430 n = notifier(ui, repo, hooktype)
428 ctx = repo[node]
431 ctx = repo[node]
429
432
430 if not n.subs:
433 if not n.subs:
431 ui.debug('notify: no subscribers to repository %s\n' % n.root)
434 ui.debug('notify: no subscribers to repository %s\n' % n.root)
432 return
435 return
433 if n.skipsource(source):
436 if n.skipsource(source):
434 ui.debug('notify: changes have source "%s" - skipping\n' % source)
437 ui.debug('notify: changes have source "%s" - skipping\n' % source)
435 return
438 return
436
439
437 ui.pushbuffer()
440 ui.pushbuffer()
438 data = ''
441 data = ''
439 count = 0
442 count = 0
440 author = ''
443 author = ''
441 if hooktype == 'changegroup' or hooktype == 'outgoing':
444 if hooktype == 'changegroup' or hooktype == 'outgoing':
442 start, end = ctx.rev(), len(repo)
445 start, end = ctx.rev(), len(repo)
443 for rev in xrange(start, end):
446 for rev in xrange(start, end):
444 if n.node(repo[rev]):
447 if n.node(repo[rev]):
445 count += 1
448 count += 1
446 if not author:
449 if not author:
447 author = repo[rev].user()
450 author = repo[rev].user()
448 else:
451 else:
449 data += ui.popbuffer()
452 data += ui.popbuffer()
450 ui.note(_('notify: suppressing notification for merge %d:%s\n')
453 ui.note(_('notify: suppressing notification for merge %d:%s\n')
451 % (rev, repo[rev].hex()[:12]))
454 % (rev, repo[rev].hex()[:12]))
452 ui.pushbuffer()
455 ui.pushbuffer()
453 if count:
456 if count:
454 n.diff(ctx, repo['tip'])
457 n.diff(ctx, repo['tip'])
455 else:
458 else:
456 if not n.node(ctx):
459 if not n.node(ctx):
457 ui.popbuffer()
460 ui.popbuffer()
458 ui.note(_('notify: suppressing notification for merge %d:%s\n') %
461 ui.note(_('notify: suppressing notification for merge %d:%s\n') %
459 (ctx.rev(), ctx.hex()[:12]))
462 (ctx.rev(), ctx.hex()[:12]))
460 return
463 return
461 count += 1
464 count += 1
462 n.diff(ctx)
465 n.diff(ctx)
463 if not author:
466 if not author:
464 author = ctx.user()
467 author = ctx.user()
465
468
466 data += ui.popbuffer()
469 data += ui.popbuffer()
467 fromauthor = ui.config('notify', 'fromauthor')
470 fromauthor = ui.config('notify', 'fromauthor')
468 if author and fromauthor:
471 if author and fromauthor:
469 data = '\n'.join(['From: %s' % author, data])
472 data = '\n'.join(['From: %s' % author, data])
470
473
471 if count:
474 if count:
472 n.send(ctx, count, data)
475 n.send(ctx, count, data)
General Comments 0
You need to be logged in to leave comments. Login now