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