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