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