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