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