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