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