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