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