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