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