##// END OF EJS Templates
configitems: register the 'notify.merge' config
Boris Feld -
r33745:8bf8ed0d default
parent child Browse files
Show More
@@ -1,457 +1,460 b''
1 # notify.py - email notifications for mercurial
1 # notify.py - email notifications for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''hooks for sending email push notifications
8 '''hooks for sending email push notifications
9
9
10 This extension implements hooks to send email notifications when
10 This extension implements hooks to send email notifications when
11 changesets are sent from or received by the local repository.
11 changesets are sent from or received by the local repository.
12
12
13 First, enable the extension as explained in :hg:`help extensions`, and
13 First, enable the extension as explained in :hg:`help extensions`, and
14 register the hook you want to run. ``incoming`` and ``changegroup`` hooks
14 register the hook you want to run. ``incoming`` and ``changegroup`` hooks
15 are run when changesets are received, while ``outgoing`` hooks are for
15 are run when changesets are received, while ``outgoing`` hooks are for
16 changesets sent to another repository::
16 changesets sent to another repository::
17
17
18 [hooks]
18 [hooks]
19 # one email for each incoming changeset
19 # one email for each incoming changeset
20 incoming.notify = python:hgext.notify.hook
20 incoming.notify = python:hgext.notify.hook
21 # one email for all incoming changesets
21 # one email for all incoming changesets
22 changegroup.notify = python:hgext.notify.hook
22 changegroup.notify = python:hgext.notify.hook
23
23
24 # one email for all outgoing changesets
24 # one email for all outgoing changesets
25 outgoing.notify = python:hgext.notify.hook
25 outgoing.notify = python:hgext.notify.hook
26
26
27 This registers the hooks. To enable notification, subscribers must
27 This registers the hooks. To enable notification, subscribers must
28 be assigned to repositories. The ``[usersubs]`` section maps multiple
28 be assigned to repositories. The ``[usersubs]`` section maps multiple
29 repositories to a given recipient. The ``[reposubs]`` section maps
29 repositories to a given recipient. The ``[reposubs]`` section maps
30 multiple recipients to a single repository::
30 multiple recipients to a single repository::
31
31
32 [usersubs]
32 [usersubs]
33 # key is subscriber email, value is a comma-separated list of repo patterns
33 # key is subscriber email, value is a comma-separated list of repo patterns
34 user@host = pattern
34 user@host = pattern
35
35
36 [reposubs]
36 [reposubs]
37 # key is repo pattern, value is a comma-separated list of subscriber emails
37 # key is repo pattern, value is a comma-separated list of subscriber emails
38 pattern = user@host
38 pattern = user@host
39
39
40 A ``pattern`` is a ``glob`` matching the absolute path to a repository,
40 A ``pattern`` is a ``glob`` matching the absolute path to a repository,
41 optionally combined with a revset expression. A revset expression, if
41 optionally combined with a revset expression. A revset expression, if
42 present, is separated from the glob by a hash. Example::
42 present, is separated from the glob by a hash. Example::
43
43
44 [reposubs]
44 [reposubs]
45 */widgets#branch(release) = qa-team@example.com
45 */widgets#branch(release) = qa-team@example.com
46
46
47 This sends to ``qa-team@example.com`` whenever a changeset on the ``release``
47 This sends to ``qa-team@example.com`` whenever a changeset on the ``release``
48 branch triggers a notification in any repository ending in ``widgets``.
48 branch triggers a notification in any repository ending in ``widgets``.
49
49
50 In order to place them under direct user management, ``[usersubs]`` and
50 In order to place them under direct user management, ``[usersubs]`` and
51 ``[reposubs]`` sections may be placed in a separate ``hgrc`` file and
51 ``[reposubs]`` sections may be placed in a separate ``hgrc`` file and
52 incorporated by reference::
52 incorporated by reference::
53
53
54 [notify]
54 [notify]
55 config = /path/to/subscriptionsfile
55 config = /path/to/subscriptionsfile
56
56
57 Notifications will not be sent until the ``notify.test`` value is set
57 Notifications will not be sent until the ``notify.test`` value is set
58 to ``False``; see below.
58 to ``False``; see below.
59
59
60 Notifications content can be tweaked with the following configuration entries:
60 Notifications content can be tweaked with the following configuration entries:
61
61
62 notify.test
62 notify.test
63 If ``True``, print messages to stdout instead of sending them. Default: True.
63 If ``True``, print messages to stdout instead of sending them. Default: True.
64
64
65 notify.sources
65 notify.sources
66 Space-separated list of change sources. Notifications are activated only
66 Space-separated list of change sources. Notifications are activated only
67 when a changeset's source is in this list. Sources may be:
67 when a changeset's source is in this list. Sources may be:
68
68
69 :``serve``: changesets received via http or ssh
69 :``serve``: changesets received via http or ssh
70 :``pull``: changesets received via ``hg pull``
70 :``pull``: changesets received via ``hg pull``
71 :``unbundle``: changesets received via ``hg unbundle``
71 :``unbundle``: changesets received via ``hg unbundle``
72 :``push``: changesets sent or received via ``hg push``
72 :``push``: changesets sent or received via ``hg push``
73 :``bundle``: changesets sent via ``hg unbundle``
73 :``bundle``: changesets sent via ``hg unbundle``
74
74
75 Default: serve.
75 Default: serve.
76
76
77 notify.strip
77 notify.strip
78 Number of leading slashes to strip from url paths. By default, notifications
78 Number of leading slashes to strip from url paths. By default, notifications
79 reference repositories with their absolute path. ``notify.strip`` lets you
79 reference repositories with their absolute path. ``notify.strip`` lets you
80 turn them into relative paths. For example, ``notify.strip=3`` will change
80 turn them into relative paths. For example, ``notify.strip=3`` will change
81 ``/long/path/repository`` into ``repository``. Default: 0.
81 ``/long/path/repository`` into ``repository``. Default: 0.
82
82
83 notify.domain
83 notify.domain
84 Default email domain for sender or recipients with no explicit domain.
84 Default email domain for sender or recipients with no explicit domain.
85
85
86 notify.style
86 notify.style
87 Style file to use when formatting emails.
87 Style file to use when formatting emails.
88
88
89 notify.template
89 notify.template
90 Template to use when formatting emails.
90 Template to use when formatting emails.
91
91
92 notify.incoming
92 notify.incoming
93 Template to use when run as an incoming hook, overriding ``notify.template``.
93 Template to use when run as an incoming hook, overriding ``notify.template``.
94
94
95 notify.outgoing
95 notify.outgoing
96 Template to use when run as an outgoing hook, overriding ``notify.template``.
96 Template to use when run as an outgoing hook, overriding ``notify.template``.
97
97
98 notify.changegroup
98 notify.changegroup
99 Template to use when running as a changegroup hook, overriding
99 Template to use when running as a changegroup hook, overriding
100 ``notify.template``.
100 ``notify.template``.
101
101
102 notify.maxdiff
102 notify.maxdiff
103 Maximum number of diff lines to include in notification email. Set to 0
103 Maximum number of diff lines to include in notification email. Set to 0
104 to disable the diff, or -1 to include all of it. Default: 300.
104 to disable the diff, or -1 to include all of it. Default: 300.
105
105
106 notify.maxsubject
106 notify.maxsubject
107 Maximum number of characters in email's subject line. Default: 67.
107 Maximum number of characters in email's subject line. Default: 67.
108
108
109 notify.diffstat
109 notify.diffstat
110 Set to True to include a diffstat before diff content. Default: True.
110 Set to True to include a diffstat before diff content. Default: True.
111
111
112 notify.merge
112 notify.merge
113 If True, send notifications for merge changesets. Default: True.
113 If True, send notifications for merge changesets. Default: True.
114
114
115 notify.mbox
115 notify.mbox
116 If set, append mails to this mbox file instead of sending. Default: None.
116 If set, append mails to this mbox file instead of sending. Default: None.
117
117
118 notify.fromauthor
118 notify.fromauthor
119 If set, use the committer of the first changeset in a changegroup for
119 If set, use the committer of the first changeset in a changegroup for
120 the "From" field of the notification mail. If not set, take the user
120 the "From" field of the notification mail. If not set, take the user
121 from the pushing repo. Default: False.
121 from the pushing repo. Default: False.
122
122
123 If set, the following entries will also be used to customize the
123 If set, the following entries will also be used to customize the
124 notifications:
124 notifications:
125
125
126 email.from
126 email.from
127 Email ``From`` address to use if none can be found in the generated
127 Email ``From`` address to use if none can be found in the generated
128 email content.
128 email content.
129
129
130 web.baseurl
130 web.baseurl
131 Root repository URL to combine with repository paths when making
131 Root repository URL to combine with repository paths when making
132 references. See also ``notify.strip``.
132 references. See also ``notify.strip``.
133
133
134 '''
134 '''
135 from __future__ import absolute_import
135 from __future__ import absolute_import
136
136
137 import email
137 import email
138 import fnmatch
138 import fnmatch
139 import socket
139 import socket
140 import time
140 import time
141
141
142 from mercurial.i18n import _
142 from mercurial.i18n import _
143 from mercurial import (
143 from mercurial import (
144 cmdutil,
144 cmdutil,
145 error,
145 error,
146 mail,
146 mail,
147 patch,
147 patch,
148 registrar,
148 registrar,
149 util,
149 util,
150 )
150 )
151
151
152 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
152 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
153 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
153 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
154 # be specifying the version(s) of Mercurial they are tested with, or
154 # be specifying the version(s) of Mercurial they are tested with, or
155 # leave the attribute unspecified.
155 # leave the attribute unspecified.
156 testedwith = 'ships-with-hg-core'
156 testedwith = 'ships-with-hg-core'
157
157
158 configtable = {}
158 configtable = {}
159 configitem = registrar.configitem(configtable)
159 configitem = registrar.configitem(configtable)
160
160
161 configitem('notify', 'config',
161 configitem('notify', 'config',
162 default=None,
162 default=None,
163 )
163 )
164 configitem('notify', 'diffstat',
164 configitem('notify', 'diffstat',
165 default=True,
165 default=True,
166 )
166 )
167 configitem('notify', 'domain',
167 configitem('notify', 'domain',
168 default=None,
168 default=None,
169 )
169 )
170 configitem('notify', 'fromauthor',
170 configitem('notify', 'fromauthor',
171 default=None,
171 default=None,
172 )
172 )
173 configitem('notify', 'maxdiff',
173 configitem('notify', 'maxdiff',
174 default=300,
174 default=300,
175 )
175 )
176 configitem('notify', 'maxsubject',
176 configitem('notify', 'maxsubject',
177 default=67,
177 default=67,
178 )
178 )
179 configitem('notify', 'mbox',
179 configitem('notify', 'mbox',
180 default=None,
180 default=None,
181 )
181 )
182 configitem('notify', 'merge',
183 default=True,
184 )
182
185
183 # template for single changeset can include email headers.
186 # template for single changeset can include email headers.
184 single_template = '''
187 single_template = '''
185 Subject: changeset in {webroot}: {desc|firstline|strip}
188 Subject: changeset in {webroot}: {desc|firstline|strip}
186 From: {author}
189 From: {author}
187
190
188 changeset {node|short} in {root}
191 changeset {node|short} in {root}
189 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
192 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
190 description:
193 description:
191 \t{desc|tabindent|strip}
194 \t{desc|tabindent|strip}
192 '''.lstrip()
195 '''.lstrip()
193
196
194 # template for multiple changesets should not contain email headers,
197 # template for multiple changesets should not contain email headers,
195 # because only first set of headers will be used and result will look
198 # because only first set of headers will be used and result will look
196 # strange.
199 # strange.
197 multiple_template = '''
200 multiple_template = '''
198 changeset {node|short} in {root}
201 changeset {node|short} in {root}
199 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
202 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
200 summary: {desc|firstline}
203 summary: {desc|firstline}
201 '''
204 '''
202
205
203 deftemplates = {
206 deftemplates = {
204 'changegroup': multiple_template,
207 'changegroup': multiple_template,
205 }
208 }
206
209
207 class notifier(object):
210 class notifier(object):
208 '''email notification class.'''
211 '''email notification class.'''
209
212
210 def __init__(self, ui, repo, hooktype):
213 def __init__(self, ui, repo, hooktype):
211 self.ui = ui
214 self.ui = ui
212 cfg = self.ui.config('notify', 'config')
215 cfg = self.ui.config('notify', 'config')
213 if cfg:
216 if cfg:
214 self.ui.readconfig(cfg, sections=['usersubs', 'reposubs'])
217 self.ui.readconfig(cfg, sections=['usersubs', 'reposubs'])
215 self.repo = repo
218 self.repo = repo
216 self.stripcount = int(self.ui.config('notify', 'strip', 0))
219 self.stripcount = int(self.ui.config('notify', 'strip', 0))
217 self.root = self.strip(self.repo.root)
220 self.root = self.strip(self.repo.root)
218 self.domain = self.ui.config('notify', 'domain')
221 self.domain = self.ui.config('notify', 'domain')
219 self.mbox = self.ui.config('notify', 'mbox')
222 self.mbox = self.ui.config('notify', 'mbox')
220 self.test = self.ui.configbool('notify', 'test', True)
223 self.test = self.ui.configbool('notify', 'test', True)
221 self.charsets = mail._charsets(self.ui)
224 self.charsets = mail._charsets(self.ui)
222 self.subs = self.subscribers()
225 self.subs = self.subscribers()
223 self.merge = self.ui.configbool('notify', 'merge', True)
226 self.merge = self.ui.configbool('notify', 'merge')
224
227
225 mapfile = None
228 mapfile = None
226 template = (self.ui.config('notify', hooktype) or
229 template = (self.ui.config('notify', hooktype) or
227 self.ui.config('notify', 'template'))
230 self.ui.config('notify', 'template'))
228 if not template:
231 if not template:
229 mapfile = self.ui.config('notify', 'style')
232 mapfile = self.ui.config('notify', 'style')
230 if not mapfile and not template:
233 if not mapfile and not template:
231 template = deftemplates.get(hooktype) or single_template
234 template = deftemplates.get(hooktype) or single_template
232 spec = cmdutil.logtemplatespec(template, mapfile)
235 spec = cmdutil.logtemplatespec(template, mapfile)
233 self.t = cmdutil.changeset_templater(self.ui, self.repo, spec,
236 self.t = cmdutil.changeset_templater(self.ui, self.repo, spec,
234 False, None, False)
237 False, None, False)
235
238
236 def strip(self, path):
239 def strip(self, path):
237 '''strip leading slashes from local path, turn into web-safe path.'''
240 '''strip leading slashes from local path, turn into web-safe path.'''
238
241
239 path = util.pconvert(path)
242 path = util.pconvert(path)
240 count = self.stripcount
243 count = self.stripcount
241 while count > 0:
244 while count > 0:
242 c = path.find('/')
245 c = path.find('/')
243 if c == -1:
246 if c == -1:
244 break
247 break
245 path = path[c + 1:]
248 path = path[c + 1:]
246 count -= 1
249 count -= 1
247 return path
250 return path
248
251
249 def fixmail(self, addr):
252 def fixmail(self, addr):
250 '''try to clean up email addresses.'''
253 '''try to clean up email addresses.'''
251
254
252 addr = util.email(addr.strip())
255 addr = util.email(addr.strip())
253 if self.domain:
256 if self.domain:
254 a = addr.find('@localhost')
257 a = addr.find('@localhost')
255 if a != -1:
258 if a != -1:
256 addr = addr[:a]
259 addr = addr[:a]
257 if '@' not in addr:
260 if '@' not in addr:
258 return addr + '@' + self.domain
261 return addr + '@' + self.domain
259 return addr
262 return addr
260
263
261 def subscribers(self):
264 def subscribers(self):
262 '''return list of email addresses of subscribers to this repo.'''
265 '''return list of email addresses of subscribers to this repo.'''
263 subs = set()
266 subs = set()
264 for user, pats in self.ui.configitems('usersubs'):
267 for user, pats in self.ui.configitems('usersubs'):
265 for pat in pats.split(','):
268 for pat in pats.split(','):
266 if '#' in pat:
269 if '#' in pat:
267 pat, revs = pat.split('#', 1)
270 pat, revs = pat.split('#', 1)
268 else:
271 else:
269 revs = None
272 revs = None
270 if fnmatch.fnmatch(self.repo.root, pat.strip()):
273 if fnmatch.fnmatch(self.repo.root, pat.strip()):
271 subs.add((self.fixmail(user), revs))
274 subs.add((self.fixmail(user), revs))
272 for pat, users in self.ui.configitems('reposubs'):
275 for pat, users in self.ui.configitems('reposubs'):
273 if '#' in pat:
276 if '#' in pat:
274 pat, revs = pat.split('#', 1)
277 pat, revs = pat.split('#', 1)
275 else:
278 else:
276 revs = None
279 revs = None
277 if fnmatch.fnmatch(self.repo.root, pat):
280 if fnmatch.fnmatch(self.repo.root, pat):
278 for user in users.split(','):
281 for user in users.split(','):
279 subs.add((self.fixmail(user), revs))
282 subs.add((self.fixmail(user), revs))
280 return [(mail.addressencode(self.ui, s, self.charsets, self.test), r)
283 return [(mail.addressencode(self.ui, s, self.charsets, self.test), r)
281 for s, r in sorted(subs)]
284 for s, r in sorted(subs)]
282
285
283 def node(self, ctx, **props):
286 def node(self, ctx, **props):
284 '''format one changeset, unless it is a suppressed merge.'''
287 '''format one changeset, unless it is a suppressed merge.'''
285 if not self.merge and len(ctx.parents()) > 1:
288 if not self.merge and len(ctx.parents()) > 1:
286 return False
289 return False
287 self.t.show(ctx, changes=ctx.changeset(),
290 self.t.show(ctx, changes=ctx.changeset(),
288 baseurl=self.ui.config('web', 'baseurl'),
291 baseurl=self.ui.config('web', 'baseurl'),
289 root=self.repo.root, webroot=self.root, **props)
292 root=self.repo.root, webroot=self.root, **props)
290 return True
293 return True
291
294
292 def skipsource(self, source):
295 def skipsource(self, source):
293 '''true if incoming changes from this source should be skipped.'''
296 '''true if incoming changes from this source should be skipped.'''
294 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
297 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
295 return source not in ok_sources
298 return source not in ok_sources
296
299
297 def send(self, ctx, count, data):
300 def send(self, ctx, count, data):
298 '''send message.'''
301 '''send message.'''
299
302
300 # Select subscribers by revset
303 # Select subscribers by revset
301 subs = set()
304 subs = set()
302 for sub, spec in self.subs:
305 for sub, spec in self.subs:
303 if spec is None:
306 if spec is None:
304 subs.add(sub)
307 subs.add(sub)
305 continue
308 continue
306 revs = self.repo.revs('%r and %d:', spec, ctx.rev())
309 revs = self.repo.revs('%r and %d:', spec, ctx.rev())
307 if len(revs):
310 if len(revs):
308 subs.add(sub)
311 subs.add(sub)
309 continue
312 continue
310 if len(subs) == 0:
313 if len(subs) == 0:
311 self.ui.debug('notify: no subscribers to selected repo '
314 self.ui.debug('notify: no subscribers to selected repo '
312 'and revset\n')
315 'and revset\n')
313 return
316 return
314
317
315 p = email.Parser.Parser()
318 p = email.Parser.Parser()
316 try:
319 try:
317 msg = p.parsestr(data)
320 msg = p.parsestr(data)
318 except email.Errors.MessageParseError as inst:
321 except email.Errors.MessageParseError as inst:
319 raise error.Abort(inst)
322 raise error.Abort(inst)
320
323
321 # store sender and subject
324 # store sender and subject
322 sender, subject = msg['From'], msg['Subject']
325 sender, subject = msg['From'], msg['Subject']
323 del msg['From'], msg['Subject']
326 del msg['From'], msg['Subject']
324
327
325 if not msg.is_multipart():
328 if not msg.is_multipart():
326 # create fresh mime message from scratch
329 # create fresh mime message from scratch
327 # (multipart templates must take care of this themselves)
330 # (multipart templates must take care of this themselves)
328 headers = msg.items()
331 headers = msg.items()
329 payload = msg.get_payload()
332 payload = msg.get_payload()
330 # for notification prefer readability over data precision
333 # for notification prefer readability over data precision
331 msg = mail.mimeencode(self.ui, payload, self.charsets, self.test)
334 msg = mail.mimeencode(self.ui, payload, self.charsets, self.test)
332 # reinstate custom headers
335 # reinstate custom headers
333 for k, v in headers:
336 for k, v in headers:
334 msg[k] = v
337 msg[k] = v
335
338
336 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
339 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
337
340
338 # try to make subject line exist and be useful
341 # try to make subject line exist and be useful
339 if not subject:
342 if not subject:
340 if count > 1:
343 if count > 1:
341 subject = _('%s: %d new changesets') % (self.root, count)
344 subject = _('%s: %d new changesets') % (self.root, count)
342 else:
345 else:
343 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
346 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
344 subject = '%s: %s' % (self.root, s)
347 subject = '%s: %s' % (self.root, s)
345 maxsubject = int(self.ui.config('notify', 'maxsubject'))
348 maxsubject = int(self.ui.config('notify', 'maxsubject'))
346 if maxsubject:
349 if maxsubject:
347 subject = util.ellipsis(subject, maxsubject)
350 subject = util.ellipsis(subject, maxsubject)
348 msg['Subject'] = mail.headencode(self.ui, subject,
351 msg['Subject'] = mail.headencode(self.ui, subject,
349 self.charsets, self.test)
352 self.charsets, self.test)
350
353
351 # try to make message have proper sender
354 # try to make message have proper sender
352 if not sender:
355 if not sender:
353 sender = self.ui.config('email', 'from') or self.ui.username()
356 sender = self.ui.config('email', 'from') or self.ui.username()
354 if '@' not in sender or '@localhost' in sender:
357 if '@' not in sender or '@localhost' in sender:
355 sender = self.fixmail(sender)
358 sender = self.fixmail(sender)
356 msg['From'] = mail.addressencode(self.ui, sender,
359 msg['From'] = mail.addressencode(self.ui, sender,
357 self.charsets, self.test)
360 self.charsets, self.test)
358
361
359 msg['X-Hg-Notification'] = 'changeset %s' % ctx
362 msg['X-Hg-Notification'] = 'changeset %s' % ctx
360 if not msg['Message-Id']:
363 if not msg['Message-Id']:
361 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
364 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
362 (ctx, int(time.time()),
365 (ctx, int(time.time()),
363 hash(self.repo.root), socket.getfqdn()))
366 hash(self.repo.root), socket.getfqdn()))
364 msg['To'] = ', '.join(sorted(subs))
367 msg['To'] = ', '.join(sorted(subs))
365
368
366 msgtext = msg.as_string()
369 msgtext = msg.as_string()
367 if self.test:
370 if self.test:
368 self.ui.write(msgtext)
371 self.ui.write(msgtext)
369 if not msgtext.endswith('\n'):
372 if not msgtext.endswith('\n'):
370 self.ui.write('\n')
373 self.ui.write('\n')
371 else:
374 else:
372 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
375 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
373 (len(subs), count))
376 (len(subs), count))
374 mail.sendmail(self.ui, util.email(msg['From']),
377 mail.sendmail(self.ui, util.email(msg['From']),
375 subs, msgtext, mbox=self.mbox)
378 subs, msgtext, mbox=self.mbox)
376
379
377 def diff(self, ctx, ref=None):
380 def diff(self, ctx, ref=None):
378
381
379 maxdiff = int(self.ui.config('notify', 'maxdiff'))
382 maxdiff = int(self.ui.config('notify', 'maxdiff'))
380 prev = ctx.p1().node()
383 prev = ctx.p1().node()
381 if ref:
384 if ref:
382 ref = ref.node()
385 ref = ref.node()
383 else:
386 else:
384 ref = ctx.node()
387 ref = ctx.node()
385 chunks = patch.diff(self.repo, prev, ref,
388 chunks = patch.diff(self.repo, prev, ref,
386 opts=patch.diffallopts(self.ui))
389 opts=patch.diffallopts(self.ui))
387 difflines = ''.join(chunks).splitlines()
390 difflines = ''.join(chunks).splitlines()
388
391
389 if self.ui.configbool('notify', 'diffstat'):
392 if self.ui.configbool('notify', 'diffstat'):
390 s = patch.diffstat(difflines)
393 s = patch.diffstat(difflines)
391 # s may be nil, don't include the header if it is
394 # s may be nil, don't include the header if it is
392 if s:
395 if s:
393 self.ui.write(_('\ndiffstat:\n\n%s') % s)
396 self.ui.write(_('\ndiffstat:\n\n%s') % s)
394
397
395 if maxdiff == 0:
398 if maxdiff == 0:
396 return
399 return
397 elif maxdiff > 0 and len(difflines) > maxdiff:
400 elif maxdiff > 0 and len(difflines) > maxdiff:
398 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
401 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
399 self.ui.write(msg % (len(difflines), maxdiff))
402 self.ui.write(msg % (len(difflines), maxdiff))
400 difflines = difflines[:maxdiff]
403 difflines = difflines[:maxdiff]
401 elif difflines:
404 elif difflines:
402 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
405 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
403
406
404 self.ui.write("\n".join(difflines))
407 self.ui.write("\n".join(difflines))
405
408
406 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
409 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
407 '''send email notifications to interested subscribers.
410 '''send email notifications to interested subscribers.
408
411
409 if used as changegroup hook, send one email for all changesets in
412 if used as changegroup hook, send one email for all changesets in
410 changegroup. else send one email per changeset.'''
413 changegroup. else send one email per changeset.'''
411
414
412 n = notifier(ui, repo, hooktype)
415 n = notifier(ui, repo, hooktype)
413 ctx = repo[node]
416 ctx = repo[node]
414
417
415 if not n.subs:
418 if not n.subs:
416 ui.debug('notify: no subscribers to repository %s\n' % n.root)
419 ui.debug('notify: no subscribers to repository %s\n' % n.root)
417 return
420 return
418 if n.skipsource(source):
421 if n.skipsource(source):
419 ui.debug('notify: changes have source "%s" - skipping\n' % source)
422 ui.debug('notify: changes have source "%s" - skipping\n' % source)
420 return
423 return
421
424
422 ui.pushbuffer()
425 ui.pushbuffer()
423 data = ''
426 data = ''
424 count = 0
427 count = 0
425 author = ''
428 author = ''
426 if hooktype == 'changegroup' or hooktype == 'outgoing':
429 if hooktype == 'changegroup' or hooktype == 'outgoing':
427 start, end = ctx.rev(), len(repo)
430 start, end = ctx.rev(), len(repo)
428 for rev in xrange(start, end):
431 for rev in xrange(start, end):
429 if n.node(repo[rev]):
432 if n.node(repo[rev]):
430 count += 1
433 count += 1
431 if not author:
434 if not author:
432 author = repo[rev].user()
435 author = repo[rev].user()
433 else:
436 else:
434 data += ui.popbuffer()
437 data += ui.popbuffer()
435 ui.note(_('notify: suppressing notification for merge %d:%s\n')
438 ui.note(_('notify: suppressing notification for merge %d:%s\n')
436 % (rev, repo[rev].hex()[:12]))
439 % (rev, repo[rev].hex()[:12]))
437 ui.pushbuffer()
440 ui.pushbuffer()
438 if count:
441 if count:
439 n.diff(ctx, repo['tip'])
442 n.diff(ctx, repo['tip'])
440 else:
443 else:
441 if not n.node(ctx):
444 if not n.node(ctx):
442 ui.popbuffer()
445 ui.popbuffer()
443 ui.note(_('notify: suppressing notification for merge %d:%s\n') %
446 ui.note(_('notify: suppressing notification for merge %d:%s\n') %
444 (ctx.rev(), ctx.hex()[:12]))
447 (ctx.rev(), ctx.hex()[:12]))
445 return
448 return
446 count += 1
449 count += 1
447 n.diff(ctx)
450 n.diff(ctx)
448 if not author:
451 if not author:
449 author = ctx.user()
452 author = ctx.user()
450
453
451 data += ui.popbuffer()
454 data += ui.popbuffer()
452 fromauthor = ui.config('notify', 'fromauthor')
455 fromauthor = ui.config('notify', 'fromauthor')
453 if author and fromauthor:
456 if author and fromauthor:
454 data = '\n'.join(['From: %s' % author, data])
457 data = '\n'.join(['From: %s' % author, data])
455
458
456 if count:
459 if count:
457 n.send(ctx, count, data)
460 n.send(ctx, count, data)
General Comments 0
You need to be logged in to leave comments. Login now