##// END OF EJS Templates
notify: do not load style file if template is specified (BC)...
Yuya Nishihara -
r28951:1bba1b43 default
parent child Browse files
Show More
@@ -1,428 +1,430
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 import (
142 from mercurial import (
143 cmdutil,
143 cmdutil,
144 error,
144 error,
145 mail,
145 mail,
146 patch,
146 patch,
147 util,
147 util,
148 )
148 )
149 from mercurial.i18n import _
149 from mercurial.i18n import _
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 = self.ui.config('notify', 'style')
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:
203 mapfile = self.ui.config('notify', 'style')
202 if not mapfile and not template:
204 if not mapfile and not template:
203 template = deftemplates.get(hooktype) or single_template
205 template = deftemplates.get(hooktype) or single_template
204 self.t = cmdutil.changeset_templater(self.ui, self.repo, False, None,
206 self.t = cmdutil.changeset_templater(self.ui, self.repo, False, None,
205 template, mapfile, False)
207 template, mapfile, False)
206
208
207 def strip(self, path):
209 def strip(self, path):
208 '''strip leading slashes from local path, turn into web-safe path.'''
210 '''strip leading slashes from local path, turn into web-safe path.'''
209
211
210 path = util.pconvert(path)
212 path = util.pconvert(path)
211 count = self.stripcount
213 count = self.stripcount
212 while count > 0:
214 while count > 0:
213 c = path.find('/')
215 c = path.find('/')
214 if c == -1:
216 if c == -1:
215 break
217 break
216 path = path[c + 1:]
218 path = path[c + 1:]
217 count -= 1
219 count -= 1
218 return path
220 return path
219
221
220 def fixmail(self, addr):
222 def fixmail(self, addr):
221 '''try to clean up email addresses.'''
223 '''try to clean up email addresses.'''
222
224
223 addr = util.email(addr.strip())
225 addr = util.email(addr.strip())
224 if self.domain:
226 if self.domain:
225 a = addr.find('@localhost')
227 a = addr.find('@localhost')
226 if a != -1:
228 if a != -1:
227 addr = addr[:a]
229 addr = addr[:a]
228 if '@' not in addr:
230 if '@' not in addr:
229 return addr + '@' + self.domain
231 return addr + '@' + self.domain
230 return addr
232 return addr
231
233
232 def subscribers(self):
234 def subscribers(self):
233 '''return list of email addresses of subscribers to this repo.'''
235 '''return list of email addresses of subscribers to this repo.'''
234 subs = set()
236 subs = set()
235 for user, pats in self.ui.configitems('usersubs'):
237 for user, pats in self.ui.configitems('usersubs'):
236 for pat in pats.split(','):
238 for pat in pats.split(','):
237 if '#' in pat:
239 if '#' in pat:
238 pat, revs = pat.split('#', 1)
240 pat, revs = pat.split('#', 1)
239 else:
241 else:
240 revs = None
242 revs = None
241 if fnmatch.fnmatch(self.repo.root, pat.strip()):
243 if fnmatch.fnmatch(self.repo.root, pat.strip()):
242 subs.add((self.fixmail(user), revs))
244 subs.add((self.fixmail(user), revs))
243 for pat, users in self.ui.configitems('reposubs'):
245 for pat, users in self.ui.configitems('reposubs'):
244 if '#' in pat:
246 if '#' in pat:
245 pat, revs = pat.split('#', 1)
247 pat, revs = pat.split('#', 1)
246 else:
248 else:
247 revs = None
249 revs = None
248 if fnmatch.fnmatch(self.repo.root, pat):
250 if fnmatch.fnmatch(self.repo.root, pat):
249 for user in users.split(','):
251 for user in users.split(','):
250 subs.add((self.fixmail(user), revs))
252 subs.add((self.fixmail(user), revs))
251 return [(mail.addressencode(self.ui, s, self.charsets, self.test), r)
253 return [(mail.addressencode(self.ui, s, self.charsets, self.test), r)
252 for s, r in sorted(subs)]
254 for s, r in sorted(subs)]
253
255
254 def node(self, ctx, **props):
256 def node(self, ctx, **props):
255 '''format one changeset, unless it is a suppressed merge.'''
257 '''format one changeset, unless it is a suppressed merge.'''
256 if not self.merge and len(ctx.parents()) > 1:
258 if not self.merge and len(ctx.parents()) > 1:
257 return False
259 return False
258 self.t.show(ctx, changes=ctx.changeset(),
260 self.t.show(ctx, changes=ctx.changeset(),
259 baseurl=self.ui.config('web', 'baseurl'),
261 baseurl=self.ui.config('web', 'baseurl'),
260 root=self.repo.root, webroot=self.root, **props)
262 root=self.repo.root, webroot=self.root, **props)
261 return True
263 return True
262
264
263 def skipsource(self, source):
265 def skipsource(self, source):
264 '''true if incoming changes from this source should be skipped.'''
266 '''true if incoming changes from this source should be skipped.'''
265 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
267 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
266 return source not in ok_sources
268 return source not in ok_sources
267
269
268 def send(self, ctx, count, data):
270 def send(self, ctx, count, data):
269 '''send message.'''
271 '''send message.'''
270
272
271 # Select subscribers by revset
273 # Select subscribers by revset
272 subs = set()
274 subs = set()
273 for sub, spec in self.subs:
275 for sub, spec in self.subs:
274 if spec is None:
276 if spec is None:
275 subs.add(sub)
277 subs.add(sub)
276 continue
278 continue
277 revs = self.repo.revs('%r and %d:', spec, ctx.rev())
279 revs = self.repo.revs('%r and %d:', spec, ctx.rev())
278 if len(revs):
280 if len(revs):
279 subs.add(sub)
281 subs.add(sub)
280 continue
282 continue
281 if len(subs) == 0:
283 if len(subs) == 0:
282 self.ui.debug('notify: no subscribers to selected repo '
284 self.ui.debug('notify: no subscribers to selected repo '
283 'and revset\n')
285 'and revset\n')
284 return
286 return
285
287
286 p = email.Parser.Parser()
288 p = email.Parser.Parser()
287 try:
289 try:
288 msg = p.parsestr(data)
290 msg = p.parsestr(data)
289 except email.Errors.MessageParseError as inst:
291 except email.Errors.MessageParseError as inst:
290 raise error.Abort(inst)
292 raise error.Abort(inst)
291
293
292 # store sender and subject
294 # store sender and subject
293 sender, subject = msg['From'], msg['Subject']
295 sender, subject = msg['From'], msg['Subject']
294 del msg['From'], msg['Subject']
296 del msg['From'], msg['Subject']
295
297
296 if not msg.is_multipart():
298 if not msg.is_multipart():
297 # create fresh mime message from scratch
299 # create fresh mime message from scratch
298 # (multipart templates must take care of this themselves)
300 # (multipart templates must take care of this themselves)
299 headers = msg.items()
301 headers = msg.items()
300 payload = msg.get_payload()
302 payload = msg.get_payload()
301 # for notification prefer readability over data precision
303 # for notification prefer readability over data precision
302 msg = mail.mimeencode(self.ui, payload, self.charsets, self.test)
304 msg = mail.mimeencode(self.ui, payload, self.charsets, self.test)
303 # reinstate custom headers
305 # reinstate custom headers
304 for k, v in headers:
306 for k, v in headers:
305 msg[k] = v
307 msg[k] = v
306
308
307 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")
308
310
309 # try to make subject line exist and be useful
311 # try to make subject line exist and be useful
310 if not subject:
312 if not subject:
311 if count > 1:
313 if count > 1:
312 subject = _('%s: %d new changesets') % (self.root, count)
314 subject = _('%s: %d new changesets') % (self.root, count)
313 else:
315 else:
314 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
316 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
315 subject = '%s: %s' % (self.root, s)
317 subject = '%s: %s' % (self.root, s)
316 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
318 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
317 if maxsubject:
319 if maxsubject:
318 subject = util.ellipsis(subject, maxsubject)
320 subject = util.ellipsis(subject, maxsubject)
319 msg['Subject'] = mail.headencode(self.ui, subject,
321 msg['Subject'] = mail.headencode(self.ui, subject,
320 self.charsets, self.test)
322 self.charsets, self.test)
321
323
322 # try to make message have proper sender
324 # try to make message have proper sender
323 if not sender:
325 if not sender:
324 sender = self.ui.config('email', 'from') or self.ui.username()
326 sender = self.ui.config('email', 'from') or self.ui.username()
325 if '@' not in sender or '@localhost' in sender:
327 if '@' not in sender or '@localhost' in sender:
326 sender = self.fixmail(sender)
328 sender = self.fixmail(sender)
327 msg['From'] = mail.addressencode(self.ui, sender,
329 msg['From'] = mail.addressencode(self.ui, sender,
328 self.charsets, self.test)
330 self.charsets, self.test)
329
331
330 msg['X-Hg-Notification'] = 'changeset %s' % ctx
332 msg['X-Hg-Notification'] = 'changeset %s' % ctx
331 if not msg['Message-Id']:
333 if not msg['Message-Id']:
332 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
334 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
333 (ctx, int(time.time()),
335 (ctx, int(time.time()),
334 hash(self.repo.root), socket.getfqdn()))
336 hash(self.repo.root), socket.getfqdn()))
335 msg['To'] = ', '.join(sorted(subs))
337 msg['To'] = ', '.join(sorted(subs))
336
338
337 msgtext = msg.as_string()
339 msgtext = msg.as_string()
338 if self.test:
340 if self.test:
339 self.ui.write(msgtext)
341 self.ui.write(msgtext)
340 if not msgtext.endswith('\n'):
342 if not msgtext.endswith('\n'):
341 self.ui.write('\n')
343 self.ui.write('\n')
342 else:
344 else:
343 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
345 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
344 (len(subs), count))
346 (len(subs), count))
345 mail.sendmail(self.ui, util.email(msg['From']),
347 mail.sendmail(self.ui, util.email(msg['From']),
346 subs, msgtext, mbox=self.mbox)
348 subs, msgtext, mbox=self.mbox)
347
349
348 def diff(self, ctx, ref=None):
350 def diff(self, ctx, ref=None):
349
351
350 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
352 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
351 prev = ctx.p1().node()
353 prev = ctx.p1().node()
352 if ref:
354 if ref:
353 ref = ref.node()
355 ref = ref.node()
354 else:
356 else:
355 ref = ctx.node()
357 ref = ctx.node()
356 chunks = patch.diff(self.repo, prev, ref,
358 chunks = patch.diff(self.repo, prev, ref,
357 opts=patch.diffallopts(self.ui))
359 opts=patch.diffallopts(self.ui))
358 difflines = ''.join(chunks).splitlines()
360 difflines = ''.join(chunks).splitlines()
359
361
360 if self.ui.configbool('notify', 'diffstat', True):
362 if self.ui.configbool('notify', 'diffstat', True):
361 s = patch.diffstat(difflines)
363 s = patch.diffstat(difflines)
362 # 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
363 if s:
365 if s:
364 self.ui.write('\ndiffstat:\n\n%s' % s)
366 self.ui.write('\ndiffstat:\n\n%s' % s)
365
367
366 if maxdiff == 0:
368 if maxdiff == 0:
367 return
369 return
368 elif maxdiff > 0 and len(difflines) > maxdiff:
370 elif maxdiff > 0 and len(difflines) > maxdiff:
369 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
371 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
370 self.ui.write(msg % (len(difflines), maxdiff))
372 self.ui.write(msg % (len(difflines), maxdiff))
371 difflines = difflines[:maxdiff]
373 difflines = difflines[:maxdiff]
372 elif difflines:
374 elif difflines:
373 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
375 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
374
376
375 self.ui.write("\n".join(difflines))
377 self.ui.write("\n".join(difflines))
376
378
377 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
379 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
378 '''send email notifications to interested subscribers.
380 '''send email notifications to interested subscribers.
379
381
380 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
381 changegroup. else send one email per changeset.'''
383 changegroup. else send one email per changeset.'''
382
384
383 n = notifier(ui, repo, hooktype)
385 n = notifier(ui, repo, hooktype)
384 ctx = repo[node]
386 ctx = repo[node]
385
387
386 if not n.subs:
388 if not n.subs:
387 ui.debug('notify: no subscribers to repository %s\n' % n.root)
389 ui.debug('notify: no subscribers to repository %s\n' % n.root)
388 return
390 return
389 if n.skipsource(source):
391 if n.skipsource(source):
390 ui.debug('notify: changes have source "%s" - skipping\n' % source)
392 ui.debug('notify: changes have source "%s" - skipping\n' % source)
391 return
393 return
392
394
393 ui.pushbuffer()
395 ui.pushbuffer()
394 data = ''
396 data = ''
395 count = 0
397 count = 0
396 author = ''
398 author = ''
397 if hooktype == 'changegroup' or hooktype == 'outgoing':
399 if hooktype == 'changegroup' or hooktype == 'outgoing':
398 start, end = ctx.rev(), len(repo)
400 start, end = ctx.rev(), len(repo)
399 for rev in xrange(start, end):
401 for rev in xrange(start, end):
400 if n.node(repo[rev]):
402 if n.node(repo[rev]):
401 count += 1
403 count += 1
402 if not author:
404 if not author:
403 author = repo[rev].user()
405 author = repo[rev].user()
404 else:
406 else:
405 data += ui.popbuffer()
407 data += ui.popbuffer()
406 ui.note(_('notify: suppressing notification for merge %d:%s\n')
408 ui.note(_('notify: suppressing notification for merge %d:%s\n')
407 % (rev, repo[rev].hex()[:12]))
409 % (rev, repo[rev].hex()[:12]))
408 ui.pushbuffer()
410 ui.pushbuffer()
409 if count:
411 if count:
410 n.diff(ctx, repo['tip'])
412 n.diff(ctx, repo['tip'])
411 else:
413 else:
412 if not n.node(ctx):
414 if not n.node(ctx):
413 ui.popbuffer()
415 ui.popbuffer()
414 ui.note(_('notify: suppressing notification for merge %d:%s\n') %
416 ui.note(_('notify: suppressing notification for merge %d:%s\n') %
415 (ctx.rev(), ctx.hex()[:12]))
417 (ctx.rev(), ctx.hex()[:12]))
416 return
418 return
417 count += 1
419 count += 1
418 n.diff(ctx)
420 n.diff(ctx)
419 if not author:
421 if not author:
420 author = ctx.user()
422 author = ctx.user()
421
423
422 data += ui.popbuffer()
424 data += ui.popbuffer()
423 fromauthor = ui.config('notify', 'fromauthor')
425 fromauthor = ui.config('notify', 'fromauthor')
424 if author and fromauthor:
426 if author and fromauthor:
425 data = '\n'.join(['From: %s' % author, data])
427 data = '\n'.join(['From: %s' % author, data])
426
428
427 if count:
429 if count:
428 n.send(ctx, count, data)
430 n.send(ctx, count, data)
@@ -1,555 +1,628
1
1
2 $ cat <<EOF >> $HGRCPATH
2 $ cat <<EOF >> $HGRCPATH
3 > [extensions]
3 > [extensions]
4 > notify=
4 > notify=
5 >
5 >
6 > [hooks]
6 > [hooks]
7 > incoming.notify = python:hgext.notify.hook
7 > incoming.notify = python:hgext.notify.hook
8 >
8 >
9 > [notify]
9 > [notify]
10 > sources = pull
10 > sources = pull
11 > diffstat = False
11 > diffstat = False
12 >
12 >
13 > [usersubs]
13 > [usersubs]
14 > foo@bar = *
14 > foo@bar = *
15 >
15 >
16 > [reposubs]
16 > [reposubs]
17 > * = baz
17 > * = baz
18 > EOF
18 > EOF
19 $ hg help notify
19 $ hg help notify
20 notify extension - hooks for sending email push notifications
20 notify extension - hooks for sending email push notifications
21
21
22 This extension implements hooks to send email notifications when changesets
22 This extension implements hooks to send email notifications when changesets
23 are sent from or received by the local repository.
23 are sent from or received by the local repository.
24
24
25 First, enable the extension as explained in 'hg help extensions', and register
25 First, enable the extension as explained in 'hg help extensions', and register
26 the hook you want to run. "incoming" and "changegroup" hooks are run when
26 the hook you want to run. "incoming" and "changegroup" hooks are run when
27 changesets are received, while "outgoing" hooks are for changesets sent to
27 changesets are received, while "outgoing" hooks are for changesets sent to
28 another repository:
28 another repository:
29
29
30 [hooks]
30 [hooks]
31 # one email for each incoming changeset
31 # one email for each incoming changeset
32 incoming.notify = python:hgext.notify.hook
32 incoming.notify = python:hgext.notify.hook
33 # one email for all incoming changesets
33 # one email for all incoming changesets
34 changegroup.notify = python:hgext.notify.hook
34 changegroup.notify = python:hgext.notify.hook
35
35
36 # one email for all outgoing changesets
36 # one email for all outgoing changesets
37 outgoing.notify = python:hgext.notify.hook
37 outgoing.notify = python:hgext.notify.hook
38
38
39 This registers the hooks. To enable notification, subscribers must be assigned
39 This registers the hooks. To enable notification, subscribers must be assigned
40 to repositories. The "[usersubs]" section maps multiple repositories to a
40 to repositories. The "[usersubs]" section maps multiple repositories to a
41 given recipient. The "[reposubs]" section maps multiple recipients to a single
41 given recipient. The "[reposubs]" section maps multiple recipients to a single
42 repository:
42 repository:
43
43
44 [usersubs]
44 [usersubs]
45 # key is subscriber email, value is a comma-separated list of repo patterns
45 # key is subscriber email, value is a comma-separated list of repo patterns
46 user@host = pattern
46 user@host = pattern
47
47
48 [reposubs]
48 [reposubs]
49 # key is repo pattern, value is a comma-separated list of subscriber emails
49 # key is repo pattern, value is a comma-separated list of subscriber emails
50 pattern = user@host
50 pattern = user@host
51
51
52 A "pattern" is a "glob" matching the absolute path to a repository, optionally
52 A "pattern" is a "glob" matching the absolute path to a repository, optionally
53 combined with a revset expression. A revset expression, if present, is
53 combined with a revset expression. A revset expression, if present, is
54 separated from the glob by a hash. Example:
54 separated from the glob by a hash. Example:
55
55
56 [reposubs]
56 [reposubs]
57 */widgets#branch(release) = qa-team@example.com
57 */widgets#branch(release) = qa-team@example.com
58
58
59 This sends to "qa-team@example.com" whenever a changeset on the "release"
59 This sends to "qa-team@example.com" whenever a changeset on the "release"
60 branch triggers a notification in any repository ending in "widgets".
60 branch triggers a notification in any repository ending in "widgets".
61
61
62 In order to place them under direct user management, "[usersubs]" and
62 In order to place them under direct user management, "[usersubs]" and
63 "[reposubs]" sections may be placed in a separate "hgrc" file and incorporated
63 "[reposubs]" sections may be placed in a separate "hgrc" file and incorporated
64 by reference:
64 by reference:
65
65
66 [notify]
66 [notify]
67 config = /path/to/subscriptionsfile
67 config = /path/to/subscriptionsfile
68
68
69 Notifications will not be sent until the "notify.test" value is set to
69 Notifications will not be sent until the "notify.test" value is set to
70 "False"; see below.
70 "False"; see below.
71
71
72 Notifications content can be tweaked with the following configuration entries:
72 Notifications content can be tweaked with the following configuration entries:
73
73
74 notify.test
74 notify.test
75 If "True", print messages to stdout instead of sending them. Default: True.
75 If "True", print messages to stdout instead of sending them. Default: True.
76
76
77 notify.sources
77 notify.sources
78 Space-separated list of change sources. Notifications are activated only
78 Space-separated list of change sources. Notifications are activated only
79 when a changeset's source is in this list. Sources may be:
79 when a changeset's source is in this list. Sources may be:
80
80
81 "serve" changesets received via http or ssh
81 "serve" changesets received via http or ssh
82 "pull" changesets received via "hg pull"
82 "pull" changesets received via "hg pull"
83 "unbundle" changesets received via "hg unbundle"
83 "unbundle" changesets received via "hg unbundle"
84 "push" changesets sent or received via "hg push"
84 "push" changesets sent or received via "hg push"
85 "bundle" changesets sent via "hg unbundle"
85 "bundle" changesets sent via "hg unbundle"
86
86
87 Default: serve.
87 Default: serve.
88
88
89 notify.strip
89 notify.strip
90 Number of leading slashes to strip from url paths. By default, notifications
90 Number of leading slashes to strip from url paths. By default, notifications
91 reference repositories with their absolute path. "notify.strip" lets you
91 reference repositories with their absolute path. "notify.strip" lets you
92 turn them into relative paths. For example, "notify.strip=3" will change
92 turn them into relative paths. For example, "notify.strip=3" will change
93 "/long/path/repository" into "repository". Default: 0.
93 "/long/path/repository" into "repository". Default: 0.
94
94
95 notify.domain
95 notify.domain
96 Default email domain for sender or recipients with no explicit domain.
96 Default email domain for sender or recipients with no explicit domain.
97
97
98 notify.style
98 notify.style
99 Style file to use when formatting emails.
99 Style file to use when formatting emails.
100
100
101 notify.template
101 notify.template
102 Template to use when formatting emails.
102 Template to use when formatting emails.
103
103
104 notify.incoming
104 notify.incoming
105 Template to use when run as an incoming hook, overriding "notify.template".
105 Template to use when run as an incoming hook, overriding "notify.template".
106
106
107 notify.outgoing
107 notify.outgoing
108 Template to use when run as an outgoing hook, overriding "notify.template".
108 Template to use when run as an outgoing hook, overriding "notify.template".
109
109
110 notify.changegroup
110 notify.changegroup
111 Template to use when running as a changegroup hook, overriding
111 Template to use when running as a changegroup hook, overriding
112 "notify.template".
112 "notify.template".
113
113
114 notify.maxdiff
114 notify.maxdiff
115 Maximum number of diff lines to include in notification email. Set to 0 to
115 Maximum number of diff lines to include in notification email. Set to 0 to
116 disable the diff, or -1 to include all of it. Default: 300.
116 disable the diff, or -1 to include all of it. Default: 300.
117
117
118 notify.maxsubject
118 notify.maxsubject
119 Maximum number of characters in email's subject line. Default: 67.
119 Maximum number of characters in email's subject line. Default: 67.
120
120
121 notify.diffstat
121 notify.diffstat
122 Set to True to include a diffstat before diff content. Default: True.
122 Set to True to include a diffstat before diff content. Default: True.
123
123
124 notify.merge
124 notify.merge
125 If True, send notifications for merge changesets. Default: True.
125 If True, send notifications for merge changesets. Default: True.
126
126
127 notify.mbox
127 notify.mbox
128 If set, append mails to this mbox file instead of sending. Default: None.
128 If set, append mails to this mbox file instead of sending. Default: None.
129
129
130 notify.fromauthor
130 notify.fromauthor
131 If set, use the committer of the first changeset in a changegroup for the
131 If set, use the committer of the first changeset in a changegroup for the
132 "From" field of the notification mail. If not set, take the user from the
132 "From" field of the notification mail. If not set, take the user from the
133 pushing repo. Default: False.
133 pushing repo. Default: False.
134
134
135 If set, the following entries will also be used to customize the
135 If set, the following entries will also be used to customize the
136 notifications:
136 notifications:
137
137
138 email.from
138 email.from
139 Email "From" address to use if none can be found in the generated email
139 Email "From" address to use if none can be found in the generated email
140 content.
140 content.
141
141
142 web.baseurl
142 web.baseurl
143 Root repository URL to combine with repository paths when making references.
143 Root repository URL to combine with repository paths when making references.
144 See also "notify.strip".
144 See also "notify.strip".
145
145
146 no commands defined
146 no commands defined
147 $ hg init a
147 $ hg init a
148 $ echo a > a/a
148 $ echo a > a/a
149
149
150 commit
150 commit
151
151
152 $ hg --cwd a commit -Ama -d '0 0'
152 $ hg --cwd a commit -Ama -d '0 0'
153 adding a
153 adding a
154
154
155
155
156 clone
156 clone
157
157
158 $ hg --traceback clone a b
158 $ hg --traceback clone a b
159 updating to branch default
159 updating to branch default
160 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
160 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
161 $ echo a >> a/a
161 $ echo a >> a/a
162
162
163 commit
163 commit
164
164
165 $ hg --traceback --cwd a commit -Amb -d '1 0'
165 $ hg --traceback --cwd a commit -Amb -d '1 0'
166
166
167 on Mac OS X 10.5 the tmp path is very long so would get stripped in the subject line
167 on Mac OS X 10.5 the tmp path is very long so would get stripped in the subject line
168
168
169 $ cat <<EOF >> $HGRCPATH
169 $ cat <<EOF >> $HGRCPATH
170 > [notify]
170 > [notify]
171 > maxsubject = 200
171 > maxsubject = 200
172 > EOF
172 > EOF
173
173
174 the python call below wraps continuation lines, which appear on Mac OS X 10.5 because
174 the python call below wraps continuation lines, which appear on Mac OS X 10.5 because
175 of the very long subject line
175 of the very long subject line
176 pull (minimal config)
176 pull (minimal config)
177
177
178 $ hg --traceback --cwd b pull ../a | \
178 $ hg --traceback --cwd b pull ../a | \
179 > $PYTHON -c 'import sys,re; print re.sub("\n[\t ]", " ", sys.stdin.read()),'
179 > $PYTHON -c 'import sys,re; print re.sub("\n[\t ]", " ", sys.stdin.read()),'
180 pulling from ../a
180 pulling from ../a
181 searching for changes
181 searching for changes
182 adding changesets
182 adding changesets
183 adding manifests
183 adding manifests
184 adding file changes
184 adding file changes
185 added 1 changesets with 1 changes to 1 files
185 added 1 changesets with 1 changes to 1 files
186 Content-Type: text/plain; charset="us-ascii"
186 Content-Type: text/plain; charset="us-ascii"
187 MIME-Version: 1.0
187 MIME-Version: 1.0
188 Content-Transfer-Encoding: 7bit
188 Content-Transfer-Encoding: 7bit
189 Date: * (glob)
189 Date: * (glob)
190 Subject: changeset in $TESTTMP/b: b
190 Subject: changeset in $TESTTMP/b: b
191 From: test
191 From: test
192 X-Hg-Notification: changeset 0647d048b600
192 X-Hg-Notification: changeset 0647d048b600
193 Message-Id: <*> (glob)
193 Message-Id: <*> (glob)
194 To: baz, foo@bar
194 To: baz, foo@bar
195
195
196 changeset 0647d048b600 in $TESTTMP/b (glob)
196 changeset 0647d048b600 in $TESTTMP/b (glob)
197 details: $TESTTMP/b?cmd=changeset;node=0647d048b600
197 details: $TESTTMP/b?cmd=changeset;node=0647d048b600
198 description: b
198 description: b
199
199
200 diffs (6 lines):
200 diffs (6 lines):
201
201
202 diff -r cb9a9f314b8b -r 0647d048b600 a
202 diff -r cb9a9f314b8b -r 0647d048b600 a
203 --- a/a Thu Jan 01 00:00:00 1970 +0000
203 --- a/a Thu Jan 01 00:00:00 1970 +0000
204 +++ b/a Thu Jan 01 00:00:01 1970 +0000
204 +++ b/a Thu Jan 01 00:00:01 1970 +0000
205 @@ -1,1 +1,2 @@ a
205 @@ -1,1 +1,2 @@ a
206 +a
206 +a
207 (run 'hg update' to get a working copy)
207 (run 'hg update' to get a working copy)
208 $ cat <<EOF >> $HGRCPATH
208 $ cat <<EOF >> $HGRCPATH
209 > [notify]
209 > [notify]
210 > config = `pwd`/.notify.conf
210 > config = `pwd`/.notify.conf
211 > domain = test.com
211 > domain = test.com
212 > strip = 42
212 > strip = 42
213 > template = Subject: {desc|firstline|strip}\nFrom: {author}\nX-Test: foo\n\nchangeset {node|short} in {webroot}\ndescription:\n\t{desc|tabindent|strip}
213 > template = Subject: {desc|firstline|strip}\nFrom: {author}\nX-Test: foo\n\nchangeset {node|short} in {webroot}\ndescription:\n\t{desc|tabindent|strip}
214 >
214 >
215 > [web]
215 > [web]
216 > baseurl = http://test/
216 > baseurl = http://test/
217 > EOF
217 > EOF
218
218
219 fail for config file is missing
219 fail for config file is missing
220
220
221 $ hg --cwd b rollback
221 $ hg --cwd b rollback
222 repository tip rolled back to revision 0 (undo pull)
222 repository tip rolled back to revision 0 (undo pull)
223 $ hg --cwd b pull ../a 2>&1 | grep 'error.*\.notify\.conf' > /dev/null && echo pull failed
223 $ hg --cwd b pull ../a 2>&1 | grep 'error.*\.notify\.conf' > /dev/null && echo pull failed
224 pull failed
224 pull failed
225 $ touch ".notify.conf"
225 $ touch ".notify.conf"
226
226
227 pull
227 pull
228
228
229 $ hg --cwd b rollback
229 $ hg --cwd b rollback
230 repository tip rolled back to revision 0 (undo pull)
230 repository tip rolled back to revision 0 (undo pull)
231 $ hg --traceback --cwd b pull ../a | \
231 $ hg --traceback --cwd b pull ../a | \
232 > $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
232 > $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
233 pulling from ../a
233 pulling from ../a
234 searching for changes
234 searching for changes
235 adding changesets
235 adding changesets
236 adding manifests
236 adding manifests
237 adding file changes
237 adding file changes
238 added 1 changesets with 1 changes to 1 files
238 added 1 changesets with 1 changes to 1 files
239 Content-Type: text/plain; charset="us-ascii"
239 Content-Type: text/plain; charset="us-ascii"
240 MIME-Version: 1.0
240 MIME-Version: 1.0
241 Content-Transfer-Encoding: 7bit
241 Content-Transfer-Encoding: 7bit
242 X-Test: foo
242 X-Test: foo
243 Date: * (glob)
243 Date: * (glob)
244 Subject: b
244 Subject: b
245 From: test@test.com
245 From: test@test.com
246 X-Hg-Notification: changeset 0647d048b600
246 X-Hg-Notification: changeset 0647d048b600
247 Message-Id: <*> (glob)
247 Message-Id: <*> (glob)
248 To: baz@test.com, foo@bar
248 To: baz@test.com, foo@bar
249
249
250 changeset 0647d048b600 in b
250 changeset 0647d048b600 in b
251 description: b
251 description: b
252 diffs (6 lines):
252 diffs (6 lines):
253
253
254 diff -r cb9a9f314b8b -r 0647d048b600 a
254 diff -r cb9a9f314b8b -r 0647d048b600 a
255 --- a/a Thu Jan 01 00:00:00 1970 +0000
255 --- a/a Thu Jan 01 00:00:00 1970 +0000
256 +++ b/a Thu Jan 01 00:00:01 1970 +0000
256 +++ b/a Thu Jan 01 00:00:01 1970 +0000
257 @@ -1,1 +1,2 @@
257 @@ -1,1 +1,2 @@
258 a
258 a
259 +a
259 +a
260 (run 'hg update' to get a working copy)
260 (run 'hg update' to get a working copy)
261
261
262 $ cat << EOF >> $HGRCPATH
262 $ cat << EOF >> $HGRCPATH
263 > [hooks]
263 > [hooks]
264 > incoming.notify = python:hgext.notify.hook
264 > incoming.notify = python:hgext.notify.hook
265 >
265 >
266 > [notify]
266 > [notify]
267 > sources = pull
267 > sources = pull
268 > diffstat = True
268 > diffstat = True
269 > EOF
269 > EOF
270
270
271 pull
271 pull
272
272
273 $ hg --cwd b rollback
273 $ hg --cwd b rollback
274 repository tip rolled back to revision 0 (undo pull)
274 repository tip rolled back to revision 0 (undo pull)
275 $ hg --traceback --cwd b pull ../a | \
275 $ hg --traceback --cwd b pull ../a | \
276 > $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
276 > $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
277 pulling from ../a
277 pulling from ../a
278 searching for changes
278 searching for changes
279 adding changesets
279 adding changesets
280 adding manifests
280 adding manifests
281 adding file changes
281 adding file changes
282 added 1 changesets with 1 changes to 1 files
282 added 1 changesets with 1 changes to 1 files
283 Content-Type: text/plain; charset="us-ascii"
283 Content-Type: text/plain; charset="us-ascii"
284 MIME-Version: 1.0
284 MIME-Version: 1.0
285 Content-Transfer-Encoding: 7bit
285 Content-Transfer-Encoding: 7bit
286 X-Test: foo
286 X-Test: foo
287 Date: * (glob)
287 Date: * (glob)
288 Subject: b
288 Subject: b
289 From: test@test.com
289 From: test@test.com
290 X-Hg-Notification: changeset 0647d048b600
290 X-Hg-Notification: changeset 0647d048b600
291 Message-Id: <*> (glob)
291 Message-Id: <*> (glob)
292 To: baz@test.com, foo@bar
292 To: baz@test.com, foo@bar
293
293
294 changeset 0647d048b600 in b
294 changeset 0647d048b600 in b
295 description: b
295 description: b
296 diffstat:
296 diffstat:
297
297
298 a | 1 +
298 a | 1 +
299 1 files changed, 1 insertions(+), 0 deletions(-)
299 1 files changed, 1 insertions(+), 0 deletions(-)
300
300
301 diffs (6 lines):
301 diffs (6 lines):
302
302
303 diff -r cb9a9f314b8b -r 0647d048b600 a
303 diff -r cb9a9f314b8b -r 0647d048b600 a
304 --- a/a Thu Jan 01 00:00:00 1970 +0000
304 --- a/a Thu Jan 01 00:00:00 1970 +0000
305 +++ b/a Thu Jan 01 00:00:01 1970 +0000
305 +++ b/a Thu Jan 01 00:00:01 1970 +0000
306 @@ -1,1 +1,2 @@
306 @@ -1,1 +1,2 @@
307 a
307 a
308 +a
308 +a
309 (run 'hg update' to get a working copy)
309 (run 'hg update' to get a working copy)
310
310
311 test merge
311 test merge
312
312
313 $ cd a
313 $ cd a
314 $ hg up -C 0
314 $ hg up -C 0
315 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
315 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
316 $ echo a >> a
316 $ echo a >> a
317 $ hg ci -Am adda2 -d '2 0'
317 $ hg ci -Am adda2 -d '2 0'
318 created new head
318 created new head
319 $ hg merge
319 $ hg merge
320 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
320 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
321 (branch merge, don't forget to commit)
321 (branch merge, don't forget to commit)
322 $ hg ci -m merge -d '3 0'
322 $ hg ci -m merge -d '3 0'
323 $ cd ..
323 $ cd ..
324 $ hg --traceback --cwd b pull ../a | \
324 $ hg --traceback --cwd b pull ../a | \
325 > $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
325 > $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
326 pulling from ../a
326 pulling from ../a
327 searching for changes
327 searching for changes
328 adding changesets
328 adding changesets
329 adding manifests
329 adding manifests
330 adding file changes
330 adding file changes
331 added 2 changesets with 0 changes to 0 files
331 added 2 changesets with 0 changes to 0 files
332 Content-Type: text/plain; charset="us-ascii"
332 Content-Type: text/plain; charset="us-ascii"
333 MIME-Version: 1.0
333 MIME-Version: 1.0
334 Content-Transfer-Encoding: 7bit
334 Content-Transfer-Encoding: 7bit
335 X-Test: foo
335 X-Test: foo
336 Date: * (glob)
336 Date: * (glob)
337 Subject: adda2
337 Subject: adda2
338 From: test@test.com
338 From: test@test.com
339 X-Hg-Notification: changeset 0a184ce6067f
339 X-Hg-Notification: changeset 0a184ce6067f
340 Message-Id: <*> (glob)
340 Message-Id: <*> (glob)
341 To: baz@test.com, foo@bar
341 To: baz@test.com, foo@bar
342
342
343 changeset 0a184ce6067f in b
343 changeset 0a184ce6067f in b
344 description: adda2
344 description: adda2
345 diffstat:
345 diffstat:
346
346
347 a | 1 +
347 a | 1 +
348 1 files changed, 1 insertions(+), 0 deletions(-)
348 1 files changed, 1 insertions(+), 0 deletions(-)
349
349
350 diffs (6 lines):
350 diffs (6 lines):
351
351
352 diff -r cb9a9f314b8b -r 0a184ce6067f a
352 diff -r cb9a9f314b8b -r 0a184ce6067f a
353 --- a/a Thu Jan 01 00:00:00 1970 +0000
353 --- a/a Thu Jan 01 00:00:00 1970 +0000
354 +++ b/a Thu Jan 01 00:00:02 1970 +0000
354 +++ b/a Thu Jan 01 00:00:02 1970 +0000
355 @@ -1,1 +1,2 @@
355 @@ -1,1 +1,2 @@
356 a
356 a
357 +a
357 +a
358 Content-Type: text/plain; charset="us-ascii"
358 Content-Type: text/plain; charset="us-ascii"
359 MIME-Version: 1.0
359 MIME-Version: 1.0
360 Content-Transfer-Encoding: 7bit
360 Content-Transfer-Encoding: 7bit
361 X-Test: foo
361 X-Test: foo
362 Date: * (glob)
362 Date: * (glob)
363 Subject: merge
363 Subject: merge
364 From: test@test.com
364 From: test@test.com
365 X-Hg-Notification: changeset 6a0cf76b2701
365 X-Hg-Notification: changeset 6a0cf76b2701
366 Message-Id: <*> (glob)
366 Message-Id: <*> (glob)
367 To: baz@test.com, foo@bar
367 To: baz@test.com, foo@bar
368
368
369 changeset 6a0cf76b2701 in b
369 changeset 6a0cf76b2701 in b
370 description: merge
370 description: merge
371 (run 'hg update' to get a working copy)
371 (run 'hg update' to get a working copy)
372
372
373 non-ascii content and truncation of multi-byte subject
373 non-ascii content and truncation of multi-byte subject
374
374
375 $ cat <<EOF >> $HGRCPATH
375 $ cat <<EOF >> $HGRCPATH
376 > [notify]
376 > [notify]
377 > maxsubject = 4
377 > maxsubject = 4
378 > EOF
378 > EOF
379 $ echo a >> a/a
379 $ echo a >> a/a
380 $ hg --cwd a --encoding utf-8 commit -A -d '0 0' \
380 $ hg --cwd a --encoding utf-8 commit -A -d '0 0' \
381 > -m `$PYTHON -c 'print "\xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4"'`
381 > -m `$PYTHON -c 'print "\xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4"'`
382 $ hg --traceback --cwd b --encoding utf-8 pull ../a | \
382 $ hg --traceback --cwd b --encoding utf-8 pull ../a | \
383 > $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
383 > $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
384 pulling from ../a
384 pulling from ../a
385 searching for changes
385 searching for changes
386 adding changesets
386 adding changesets
387 adding manifests
387 adding manifests
388 adding file changes
388 adding file changes
389 added 1 changesets with 1 changes to 1 files
389 added 1 changesets with 1 changes to 1 files
390 Content-Type: text/plain; charset="us-ascii"
390 Content-Type: text/plain; charset="us-ascii"
391 MIME-Version: 1.0
391 MIME-Version: 1.0
392 Content-Transfer-Encoding: 8bit
392 Content-Transfer-Encoding: 8bit
393 X-Test: foo
393 X-Test: foo
394 Date: * (glob)
394 Date: * (glob)
395 Subject: \xc3\xa0... (esc)
395 Subject: \xc3\xa0... (esc)
396 From: test@test.com
396 From: test@test.com
397 X-Hg-Notification: changeset 7ea05ad269dc
397 X-Hg-Notification: changeset 7ea05ad269dc
398 Message-Id: <*> (glob)
398 Message-Id: <*> (glob)
399 To: baz@test.com, foo@bar
399 To: baz@test.com, foo@bar
400
400
401 changeset 7ea05ad269dc in b
401 changeset 7ea05ad269dc in b
402 description: \xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4 (esc)
402 description: \xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4 (esc)
403 diffstat:
403 diffstat:
404
404
405 a | 1 +
405 a | 1 +
406 1 files changed, 1 insertions(+), 0 deletions(-)
406 1 files changed, 1 insertions(+), 0 deletions(-)
407
407
408 diffs (7 lines):
408 diffs (7 lines):
409
409
410 diff -r 6a0cf76b2701 -r 7ea05ad269dc a
410 diff -r 6a0cf76b2701 -r 7ea05ad269dc a
411 --- a/a Thu Jan 01 00:00:03 1970 +0000
411 --- a/a Thu Jan 01 00:00:03 1970 +0000
412 +++ b/a Thu Jan 01 00:00:00 1970 +0000
412 +++ b/a Thu Jan 01 00:00:00 1970 +0000
413 @@ -1,2 +1,3 @@
413 @@ -1,2 +1,3 @@
414 a
414 a
415 a
415 a
416 +a
416 +a
417 (run 'hg update' to get a working copy)
417 (run 'hg update' to get a working copy)
418
418
419 long lines
419 long lines
420
420
421 $ cat <<EOF >> $HGRCPATH
421 $ cat <<EOF >> $HGRCPATH
422 > [notify]
422 > [notify]
423 > maxsubject = 67
423 > maxsubject = 67
424 > test = False
424 > test = False
425 > mbox = mbox
425 > mbox = mbox
426 > EOF
426 > EOF
427 $ $PYTHON -c 'file("a/a", "ab").write("no" * 500 + "\n")'
427 $ $PYTHON -c 'file("a/a", "ab").write("no" * 500 + "\n")'
428 $ hg --cwd a commit -A -m "long line"
428 $ hg --cwd a commit -A -m "long line"
429 $ hg --traceback --cwd b pull ../a
429 $ hg --traceback --cwd b pull ../a
430 pulling from ../a
430 pulling from ../a
431 searching for changes
431 searching for changes
432 adding changesets
432 adding changesets
433 adding manifests
433 adding manifests
434 adding file changes
434 adding file changes
435 added 1 changesets with 1 changes to 1 files
435 added 1 changesets with 1 changes to 1 files
436 notify: sending 2 subscribers 1 changes
436 notify: sending 2 subscribers 1 changes
437 (run 'hg update' to get a working copy)
437 (run 'hg update' to get a working copy)
438 $ $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", file("b/mbox").read()),'
438 $ $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", file("b/mbox").read()),'
439 From test@test.com ... ... .. ..:..:.. .... (re)
439 From test@test.com ... ... .. ..:..:.. .... (re)
440 Content-Type: text/plain; charset="us-ascii"
440 Content-Type: text/plain; charset="us-ascii"
441 MIME-Version: 1.0
441 MIME-Version: 1.0
442 Content-Transfer-Encoding: quoted-printable
442 Content-Transfer-Encoding: quoted-printable
443 X-Test: foo
443 X-Test: foo
444 Date: * (glob)
444 Date: * (glob)
445 Subject: long line
445 Subject: long line
446 From: test@test.com
446 From: test@test.com
447 X-Hg-Notification: changeset e0be44cf638b
447 X-Hg-Notification: changeset e0be44cf638b
448 Message-Id: <hg.e0be44cf638b.*.*@*> (glob)
448 Message-Id: <hg.e0be44cf638b.*.*@*> (glob)
449 To: baz@test.com, foo@bar
449 To: baz@test.com, foo@bar
450
450
451 changeset e0be44cf638b in b
451 changeset e0be44cf638b in b
452 description: long line
452 description: long line
453 diffstat:
453 diffstat:
454
454
455 a | 1 +
455 a | 1 +
456 1 files changed, 1 insertions(+), 0 deletions(-)
456 1 files changed, 1 insertions(+), 0 deletions(-)
457
457
458 diffs (8 lines):
458 diffs (8 lines):
459
459
460 diff -r 7ea05ad269dc -r e0be44cf638b a
460 diff -r 7ea05ad269dc -r e0be44cf638b a
461 --- a/a Thu Jan 01 00:00:00 1970 +0000
461 --- a/a Thu Jan 01 00:00:00 1970 +0000
462 +++ b/a Thu Jan 01 00:00:00 1970 +0000
462 +++ b/a Thu Jan 01 00:00:00 1970 +0000
463 @@ -1,3 +1,4 @@
463 @@ -1,3 +1,4 @@
464 a
464 a
465 a
465 a
466 a
466 a
467 +nonononononononononononononononononononononononononononononononononononono=
467 +nonononononononononononononononononononononononononononononononononononono=
468 nononononononononononononononononononononononononononononononononononononon=
468 nononononononononononononononononononononononononononononononononononononon=
469 ononononononononononononononononononononononononononononononononononononono=
469 ononononononononononononononononononononononononononononononononononononono=
470 nononononononononononononononononononononononononononononononononononononon=
470 nononononononononononononononononononononononononononononononononononononon=
471 ononononononononononononononononononononononononononononononononononononono=
471 ononononononononononononononononononononononononononononononononononononono=
472 nononononononononononononononononononononononononononononononononononononon=
472 nononononononononononononononononononononononononononononononononononononon=
473 ononononononononononononononononononononononononononononononononononononono=
473 ononononononononononononononononononononononononononononononononononononono=
474 nononononononononononononononononononononononononononononononononononononon=
474 nononononononononononononononononononononononononononononononononononononon=
475 ononononononononononononononononononononononononononononononononononononono=
475 ononononononononononononononononononononononononononononononononononononono=
476 nononononononononononononononononononononononononononononononononononononon=
476 nononononononononononononononononononononononononononononononononononononon=
477 ononononononononononononononononononononononononononononononononononononono=
477 ononononononononononononononononononononononononononononononononononononono=
478 nononononononononononononononononononononononononononononononononononononon=
478 nononononononononononononononononononononononononononononononononononononon=
479 ononononononononononononononononononononononononononononononononononononono=
479 ononononononononononononononononononononononononononononononononononononono=
480 nonononononononononononono
480 nonononononononononononono
481
481
482 revset selection: send to address that matches branch and repo
482 revset selection: send to address that matches branch and repo
483
483
484 $ cat << EOF >> $HGRCPATH
484 $ cat << EOF >> $HGRCPATH
485 > [hooks]
485 > [hooks]
486 > incoming.notify = python:hgext.notify.hook
486 > incoming.notify = python:hgext.notify.hook
487 >
487 >
488 > [notify]
488 > [notify]
489 > sources = pull
489 > sources = pull
490 > test = True
490 > test = True
491 > diffstat = False
491 > diffstat = False
492 > maxdiff = 0
492 > maxdiff = 0
493 >
493 >
494 > [reposubs]
494 > [reposubs]
495 > */a#branch(test) = will_no_be_send@example.com
495 > */a#branch(test) = will_no_be_send@example.com
496 > */b#branch(test) = notify@example.com
496 > */b#branch(test) = notify@example.com
497 > EOF
497 > EOF
498 $ hg --cwd a branch test
498 $ hg --cwd a branch test
499 marked working directory as branch test
499 marked working directory as branch test
500 (branches are permanent and global, did you want a bookmark?)
500 (branches are permanent and global, did you want a bookmark?)
501 $ echo a >> a/a
501 $ echo a >> a/a
502 $ hg --cwd a ci -m test -d '1 0'
502 $ hg --cwd a ci -m test -d '1 0'
503 $ hg --traceback --cwd b pull ../a | \
503 $ hg --traceback --cwd b pull ../a | \
504 > $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
504 > $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
505 pulling from ../a
505 pulling from ../a
506 searching for changes
506 searching for changes
507 adding changesets
507 adding changesets
508 adding manifests
508 adding manifests
509 adding file changes
509 adding file changes
510 added 1 changesets with 1 changes to 1 files
510 added 1 changesets with 1 changes to 1 files
511 Content-Type: text/plain; charset="us-ascii"
511 Content-Type: text/plain; charset="us-ascii"
512 MIME-Version: 1.0
512 MIME-Version: 1.0
513 Content-Transfer-Encoding: 7bit
513 Content-Transfer-Encoding: 7bit
514 X-Test: foo
514 X-Test: foo
515 Date: * (glob)
515 Date: * (glob)
516 Subject: test
516 Subject: test
517 From: test@test.com
517 From: test@test.com
518 X-Hg-Notification: changeset fbbcbc516f2f
518 X-Hg-Notification: changeset fbbcbc516f2f
519 Message-Id: <hg.fbbcbc516f2f.*.*@*> (glob)
519 Message-Id: <hg.fbbcbc516f2f.*.*@*> (glob)
520 To: baz@test.com, foo@bar, notify@example.com
520 To: baz@test.com, foo@bar, notify@example.com
521
521
522 changeset fbbcbc516f2f in b
522 changeset fbbcbc516f2f in b
523 description: test
523 description: test
524 (run 'hg update' to get a working copy)
524 (run 'hg update' to get a working copy)
525
525
526 revset selection: don't send to address that waits for mails
526 revset selection: don't send to address that waits for mails
527 from different branch
527 from different branch
528
528
529 $ hg --cwd a update default
529 $ hg --cwd a update default
530 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
530 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
531 $ echo a >> a/a
531 $ echo a >> a/a
532 $ hg --cwd a ci -m test -d '1 0'
532 $ hg --cwd a ci -m test -d '1 0'
533 $ hg --traceback --cwd b pull ../a | \
533 $ hg --traceback --cwd b pull ../a | \
534 > $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
534 > $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
535 pulling from ../a
535 pulling from ../a
536 searching for changes
536 searching for changes
537 adding changesets
537 adding changesets
538 adding manifests
538 adding manifests
539 adding file changes
539 adding file changes
540 added 1 changesets with 0 changes to 0 files (+1 heads)
540 added 1 changesets with 0 changes to 0 files (+1 heads)
541 Content-Type: text/plain; charset="us-ascii"
541 Content-Type: text/plain; charset="us-ascii"
542 MIME-Version: 1.0
542 MIME-Version: 1.0
543 Content-Transfer-Encoding: 7bit
543 Content-Transfer-Encoding: 7bit
544 X-Test: foo
544 X-Test: foo
545 Date: * (glob)
545 Date: * (glob)
546 Subject: test
546 Subject: test
547 From: test@test.com
547 From: test@test.com
548 X-Hg-Notification: changeset 38b42fa092de
548 X-Hg-Notification: changeset 38b42fa092de
549 Message-Id: <hg.38b42fa092de.*.*@*> (glob)
549 Message-Id: <hg.38b42fa092de.*.*@*> (glob)
550 To: baz@test.com, foo@bar
550 To: baz@test.com, foo@bar
551
551
552 changeset 38b42fa092de in b
552 changeset 38b42fa092de in b
553 description: test
553 description: test
554 (run 'hg heads' to see heads)
554 (run 'hg heads' to see heads)
555
555
556 default template:
557
558 $ grep -v '^template =' $HGRCPATH > "$HGRCPATH.new"
559 $ mv "$HGRCPATH.new" $HGRCPATH
560 $ echo a >> a/a
561 $ hg --cwd a commit -m 'default template'
562 $ hg --cwd b pull ../a -q | \
563 > $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
564 Content-Type: text/plain; charset="us-ascii"
565 MIME-Version: 1.0
566 Content-Transfer-Encoding: 7bit
567 Date: * (glob)
568 Subject: changeset in b: default template
569 From: test@test.com
570 X-Hg-Notification: changeset 3548c9e294b6
571 Message-Id: <hg.3548c9e294b6.*.*@*> (glob)
572 To: baz@test.com, foo@bar
573
574 changeset 3548c9e294b6 in $TESTTMP/b
575 details: http://test/b?cmd=changeset;node=3548c9e294b6
576 description: default template
577
578 with style:
579
580 $ cat <<EOF > notifystyle.map
581 > changeset = "Subject: {desc|firstline|strip}
582 > From: {author}
583 > {""}
584 > changeset {node|short}"
585 > EOF
586 $ cat <<EOF >> $HGRCPATH
587 > [notify]
588 > style = $TESTTMP/notifystyle.map
589 > EOF
590 $ echo a >> a/a
591 $ hg --cwd a commit -m 'with style'
592 $ hg --cwd b pull ../a -q | \
593 > $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
594 Content-Type: text/plain; charset="us-ascii"
595 MIME-Version: 1.0
596 Content-Transfer-Encoding: 7bit
597 Date: * (glob)
598 Subject: with style
599 From: test@test.com
600 X-Hg-Notification: changeset e917dbd961d3
601 Message-Id: <hg.e917dbd961d3.*.*@*> (glob)
602 To: baz@test.com, foo@bar
603
604 changeset e917dbd961d3
605
606 with template (overrides style):
607
608 $ cat <<EOF >> $HGRCPATH
609 > template = Subject: {node|short}: {desc|firstline|strip}
610 > From: {author}
611 > {""}
612 > {desc}
613 > EOF
614 $ echo a >> a/a
615 $ hg --cwd a commit -m 'with template'
616 $ hg --cwd b pull ../a -q | \
617 > $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
618 Content-Type: text/plain; charset="us-ascii"
619 MIME-Version: 1.0
620 Content-Transfer-Encoding: 7bit
621 Date: * (glob)
622 Subject: a09743fd3edd: with template
623 From: test@test.com
624 X-Hg-Notification: changeset a09743fd3edd
625 Message-Id: <hg.a09743fd3edd.*.*@*> (glob)
626 To: baz@test.com, foo@bar
627
628 with template
General Comments 0
You need to be logged in to leave comments. Login now