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