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