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