##// END OF EJS Templates
notify: various fixes to docstring...
David Champion -
r16950:0fdd8193 stable
parent child Browse files
Show More
@@ -1,376 +1,380 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 let you run hooks sending email notifications when
10 This extension implements hooks to send email notifications when
11 changesets are being pushed, from the sending or receiving side.
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 by the changesets receiver while the ``outgoing`` one is for
15 are run when changesets are received, while ``outgoing`` hooks are for
16 the sender::
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 Now the hooks are running, subscribers must be assigned to
27 This registers the hooks. To enable notification, subscribers must
28 repositories. Use the ``[usersubs]`` section to map repositories to a
28 be assigned to repositories. The ``[usersubs]`` section maps multiple
29 given email or the ``[reposubs]`` section to map emails to a single
29 repositories to a given recipient. The ``[reposubs]`` section maps
30 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 glob
33 # key is subscriber email, value is a comma-separated list of repo glob
34 # patterns
34 # patterns
35 user@host = pattern
35 user@host = pattern
36
36
37 [reposubs]
37 [reposubs]
38 # key is glob pattern, value is a comma-separated list of subscriber
38 # key is glob pattern, value is a comma-separated list of subscriber
39 # emails
39 # emails
40 pattern = user@host
40 pattern = user@host
41
41
42 Glob patterns are matched against absolute path to repository
42 Glob patterns are matched against absolute path to repository
43 root. The subscriptions can be defined in their own file and
43 root.
44 referenced with::
44
45 In order to place them under direct user management, ``[usersubs]`` and
46 ``[reposubs]`` sections may be placed in a separate ``hgrc`` file and
47 incorporated by reference::
45
48
46 [notify]
49 [notify]
47 config = /path/to/subscriptionsfile
50 config = /path/to/subscriptionsfile
48
51
49 Alternatively, they can be added to Mercurial configuration files by
52 Notifications will not be sent until the ``notify.test`` value is set
50 setting the previous entry to an empty value.
53 to ``False``; see below.
51
52 At this point, notifications should be generated but will not be sent until you
53 set the ``notify.test`` entry to ``False``.
54
54
55 Notifications content can be tweaked with the following configuration entries:
55 Notifications content can be tweaked with the following configuration entries:
56
56
57 notify.test
57 notify.test
58 If ``True``, print messages to stdout instead of sending them. Default: True.
58 If ``True``, print messages to stdout instead of sending them. Default: True.
59
59
60 notify.sources
60 notify.sources
61 Space separated list of change sources. Notifications are sent only
61 Space-separated list of change sources. Notifications are activated only
62 if it includes the incoming or outgoing changes source. Incoming
62 when a changeset's source is in this list. Sources may be:
63 sources can be ``serve`` for changes coming from http or ssh,
63
64 ``pull`` for pulled changes, ``unbundle`` for changes added by
64 :``serve``: changesets received via http or ssh
65 :hg:`unbundle` or ``push`` for changes being pushed
65 :``pull``: changesets received via ``hg pull``
66 locally. Outgoing sources are the same except for ``unbundle`` which
66 :``unbundle``: changesets received via ``hg unbundle``
67 is replaced by ``bundle``. Default: serve.
67 :``push``: changesets sent or received via ``hg push``
68 :``bundle``: changesets sent via ``hg unbundle``
69
70 Default: serve.
68
71
69 notify.strip
72 notify.strip
70 Number of leading slashes to strip from url paths. By default, notifications
73 Number of leading slashes to strip from url paths. By default, notifications
71 references repositories with their absolute path. ``notify.strip`` let you
74 reference repositories with their absolute path. ``notify.strip`` lets you
72 turn them into relative paths. For example, ``notify.strip=3`` will change
75 turn them into relative paths. For example, ``notify.strip=3`` will change
73 ``/long/path/repository`` into ``repository``. Default: 0.
76 ``/long/path/repository`` into ``repository``. Default: 0.
74
77
75 notify.domain
78 notify.domain
76 If subscribers emails or the from email have no domain set, complete them
79 Default email domain for sender or recipients with no explicit domain.
77 with this value.
78
80
79 notify.style
81 notify.style
80 Style file to use when formatting emails.
82 Style file to use when formatting emails.
81
83
82 notify.template
84 notify.template
83 Template to use when formatting emails.
85 Template to use when formatting emails.
84
86
85 notify.incoming
87 notify.incoming
86 Template to use when run as incoming hook, override ``notify.template``.
88 Template to use when run as an incoming hook, overriding ``notify.template``.
87
89
88 notify.outgoing
90 notify.outgoing
89 Template to use when run as outgoing hook, override ``notify.template``.
91 Template to use when run as an outgoing hook, overriding ``notify.template``.
90
92
91 notify.changegroup
93 notify.changegroup
92 Template to use when running as changegroup hook, override
94 Template to use when running as a changegroup hook, overriding
93 ``notify.template``.
95 ``notify.template``.
94
96
95 notify.maxdiff
97 notify.maxdiff
96 Maximum number of diff lines to include in notification email. Set to 0
98 Maximum number of diff lines to include in notification email. Set to 0
97 to disable the diff, -1 to include all of it. Default: 300.
99 to disable the diff, or -1 to include all of it. Default: 300.
98
100
99 notify.maxsubject
101 notify.maxsubject
100 Maximum number of characters in emails subject line. Default: 67.
102 Maximum number of characters in email's subject line. Default: 67.
101
103
102 notify.diffstat
104 notify.diffstat
103 Set to True to include a diffstat before diff content. Default: True.
105 Set to True to include a diffstat before diff content. Default: True.
104
106
105 notify.merge
107 notify.merge
106 If True, send notifications for merge changesets. Default: True.
108 If True, send notifications for merge changesets. Default: True.
107
109
108 notify.mbox
110 notify.mbox
109 If set, append mails to this mbox file instead of sending. Default: None.
111 If set, append mails to this mbox file instead of sending. Default: None.
110
112
111 notify.fromauthor
113 notify.fromauthor
112 If set, use the first committer of the changegroup for the "From" field of
114 If set, use the committer of the first changeset in a changegroup for
113 the notification mail. If not set, take the user from the pushing repo.
115 the "From" field of the notification mail. If not set, take the user
114 Default: False.
116 from the pushing repo. Default: False.
115
117
116 If set, the following entries will also be used to customize the notifications:
118 If set, the following entries will also be used to customize the
119 notifications:
117
120
118 email.from
121 email.from
119 Email ``From`` address to use if none can be found in generated email content.
122 Email ``From`` address to use if none can be found in the generated
123 email content.
120
124
121 web.baseurl
125 web.baseurl
122 Root repository browsing URL to combine with repository paths when making
126 Root repository URL to combine with repository paths when making
123 references. See also ``notify.strip``.
127 references. See also ``notify.strip``.
124
128
125 '''
129 '''
126
130
127 from mercurial.i18n import _
131 from mercurial.i18n import _
128 from mercurial import patch, cmdutil, templater, util, mail
132 from mercurial import patch, cmdutil, templater, util, mail
129 import email.Parser, email.Errors, fnmatch, socket, time
133 import email.Parser, email.Errors, fnmatch, socket, time
130
134
131 # template for single changeset can include email headers.
135 # template for single changeset can include email headers.
132 single_template = '''
136 single_template = '''
133 Subject: changeset in {webroot}: {desc|firstline|strip}
137 Subject: changeset in {webroot}: {desc|firstline|strip}
134 From: {author}
138 From: {author}
135
139
136 changeset {node|short} in {root}
140 changeset {node|short} in {root}
137 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
141 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
138 description:
142 description:
139 \t{desc|tabindent|strip}
143 \t{desc|tabindent|strip}
140 '''.lstrip()
144 '''.lstrip()
141
145
142 # template for multiple changesets should not contain email headers,
146 # template for multiple changesets should not contain email headers,
143 # because only first set of headers will be used and result will look
147 # because only first set of headers will be used and result will look
144 # strange.
148 # strange.
145 multiple_template = '''
149 multiple_template = '''
146 changeset {node|short} in {root}
150 changeset {node|short} in {root}
147 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
151 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
148 summary: {desc|firstline}
152 summary: {desc|firstline}
149 '''
153 '''
150
154
151 deftemplates = {
155 deftemplates = {
152 'changegroup': multiple_template,
156 'changegroup': multiple_template,
153 }
157 }
154
158
155 class notifier(object):
159 class notifier(object):
156 '''email notification class.'''
160 '''email notification class.'''
157
161
158 def __init__(self, ui, repo, hooktype):
162 def __init__(self, ui, repo, hooktype):
159 self.ui = ui
163 self.ui = ui
160 cfg = self.ui.config('notify', 'config')
164 cfg = self.ui.config('notify', 'config')
161 if cfg:
165 if cfg:
162 self.ui.readconfig(cfg, sections=['usersubs', 'reposubs'])
166 self.ui.readconfig(cfg, sections=['usersubs', 'reposubs'])
163 self.repo = repo
167 self.repo = repo
164 self.stripcount = int(self.ui.config('notify', 'strip', 0))
168 self.stripcount = int(self.ui.config('notify', 'strip', 0))
165 self.root = self.strip(self.repo.root)
169 self.root = self.strip(self.repo.root)
166 self.domain = self.ui.config('notify', 'domain')
170 self.domain = self.ui.config('notify', 'domain')
167 self.mbox = self.ui.config('notify', 'mbox')
171 self.mbox = self.ui.config('notify', 'mbox')
168 self.test = self.ui.configbool('notify', 'test', True)
172 self.test = self.ui.configbool('notify', 'test', True)
169 self.charsets = mail._charsets(self.ui)
173 self.charsets = mail._charsets(self.ui)
170 self.subs = self.subscribers()
174 self.subs = self.subscribers()
171 self.merge = self.ui.configbool('notify', 'merge', True)
175 self.merge = self.ui.configbool('notify', 'merge', True)
172
176
173 mapfile = self.ui.config('notify', 'style')
177 mapfile = self.ui.config('notify', 'style')
174 template = (self.ui.config('notify', hooktype) or
178 template = (self.ui.config('notify', hooktype) or
175 self.ui.config('notify', 'template'))
179 self.ui.config('notify', 'template'))
176 self.t = cmdutil.changeset_templater(self.ui, self.repo,
180 self.t = cmdutil.changeset_templater(self.ui, self.repo,
177 False, None, mapfile, False)
181 False, None, mapfile, False)
178 if not mapfile and not template:
182 if not mapfile and not template:
179 template = deftemplates.get(hooktype) or single_template
183 template = deftemplates.get(hooktype) or single_template
180 if template:
184 if template:
181 template = templater.parsestring(template, quoted=False)
185 template = templater.parsestring(template, quoted=False)
182 self.t.use_template(template)
186 self.t.use_template(template)
183
187
184 def strip(self, path):
188 def strip(self, path):
185 '''strip leading slashes from local path, turn into web-safe path.'''
189 '''strip leading slashes from local path, turn into web-safe path.'''
186
190
187 path = util.pconvert(path)
191 path = util.pconvert(path)
188 count = self.stripcount
192 count = self.stripcount
189 while count > 0:
193 while count > 0:
190 c = path.find('/')
194 c = path.find('/')
191 if c == -1:
195 if c == -1:
192 break
196 break
193 path = path[c + 1:]
197 path = path[c + 1:]
194 count -= 1
198 count -= 1
195 return path
199 return path
196
200
197 def fixmail(self, addr):
201 def fixmail(self, addr):
198 '''try to clean up email addresses.'''
202 '''try to clean up email addresses.'''
199
203
200 addr = util.email(addr.strip())
204 addr = util.email(addr.strip())
201 if self.domain:
205 if self.domain:
202 a = addr.find('@localhost')
206 a = addr.find('@localhost')
203 if a != -1:
207 if a != -1:
204 addr = addr[:a]
208 addr = addr[:a]
205 if '@' not in addr:
209 if '@' not in addr:
206 return addr + '@' + self.domain
210 return addr + '@' + self.domain
207 return addr
211 return addr
208
212
209 def subscribers(self):
213 def subscribers(self):
210 '''return list of email addresses of subscribers to this repo.'''
214 '''return list of email addresses of subscribers to this repo.'''
211 subs = set()
215 subs = set()
212 for user, pats in self.ui.configitems('usersubs'):
216 for user, pats in self.ui.configitems('usersubs'):
213 for pat in pats.split(','):
217 for pat in pats.split(','):
214 if fnmatch.fnmatch(self.repo.root, pat.strip()):
218 if fnmatch.fnmatch(self.repo.root, pat.strip()):
215 subs.add(self.fixmail(user))
219 subs.add(self.fixmail(user))
216 for pat, users in self.ui.configitems('reposubs'):
220 for pat, users in self.ui.configitems('reposubs'):
217 if fnmatch.fnmatch(self.repo.root, pat):
221 if fnmatch.fnmatch(self.repo.root, pat):
218 for user in users.split(','):
222 for user in users.split(','):
219 subs.add(self.fixmail(user))
223 subs.add(self.fixmail(user))
220 return [mail.addressencode(self.ui, s, self.charsets, self.test)
224 return [mail.addressencode(self.ui, s, self.charsets, self.test)
221 for s in sorted(subs)]
225 for s in sorted(subs)]
222
226
223 def node(self, ctx, **props):
227 def node(self, ctx, **props):
224 '''format one changeset, unless it is a suppressed merge.'''
228 '''format one changeset, unless it is a suppressed merge.'''
225 if not self.merge and len(ctx.parents()) > 1:
229 if not self.merge and len(ctx.parents()) > 1:
226 return False
230 return False
227 self.t.show(ctx, changes=ctx.changeset(),
231 self.t.show(ctx, changes=ctx.changeset(),
228 baseurl=self.ui.config('web', 'baseurl'),
232 baseurl=self.ui.config('web', 'baseurl'),
229 root=self.repo.root, webroot=self.root, **props)
233 root=self.repo.root, webroot=self.root, **props)
230 return True
234 return True
231
235
232 def skipsource(self, source):
236 def skipsource(self, source):
233 '''true if incoming changes from this source should be skipped.'''
237 '''true if incoming changes from this source should be skipped.'''
234 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
238 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
235 return source not in ok_sources
239 return source not in ok_sources
236
240
237 def send(self, ctx, count, data):
241 def send(self, ctx, count, data):
238 '''send message.'''
242 '''send message.'''
239
243
240 p = email.Parser.Parser()
244 p = email.Parser.Parser()
241 try:
245 try:
242 msg = p.parsestr(data)
246 msg = p.parsestr(data)
243 except email.Errors.MessageParseError, inst:
247 except email.Errors.MessageParseError, inst:
244 raise util.Abort(inst)
248 raise util.Abort(inst)
245
249
246 # store sender and subject
250 # store sender and subject
247 sender, subject = msg['From'], msg['Subject']
251 sender, subject = msg['From'], msg['Subject']
248 del msg['From'], msg['Subject']
252 del msg['From'], msg['Subject']
249
253
250 if not msg.is_multipart():
254 if not msg.is_multipart():
251 # create fresh mime message from scratch
255 # create fresh mime message from scratch
252 # (multipart templates must take care of this themselves)
256 # (multipart templates must take care of this themselves)
253 headers = msg.items()
257 headers = msg.items()
254 payload = msg.get_payload()
258 payload = msg.get_payload()
255 # for notification prefer readability over data precision
259 # for notification prefer readability over data precision
256 msg = mail.mimeencode(self.ui, payload, self.charsets, self.test)
260 msg = mail.mimeencode(self.ui, payload, self.charsets, self.test)
257 # reinstate custom headers
261 # reinstate custom headers
258 for k, v in headers:
262 for k, v in headers:
259 msg[k] = v
263 msg[k] = v
260
264
261 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
265 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
262
266
263 # try to make subject line exist and be useful
267 # try to make subject line exist and be useful
264 if not subject:
268 if not subject:
265 if count > 1:
269 if count > 1:
266 subject = _('%s: %d new changesets') % (self.root, count)
270 subject = _('%s: %d new changesets') % (self.root, count)
267 else:
271 else:
268 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
272 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
269 subject = '%s: %s' % (self.root, s)
273 subject = '%s: %s' % (self.root, s)
270 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
274 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
271 if maxsubject:
275 if maxsubject:
272 subject = util.ellipsis(subject, maxsubject)
276 subject = util.ellipsis(subject, maxsubject)
273 msg['Subject'] = mail.headencode(self.ui, subject,
277 msg['Subject'] = mail.headencode(self.ui, subject,
274 self.charsets, self.test)
278 self.charsets, self.test)
275
279
276 # try to make message have proper sender
280 # try to make message have proper sender
277 if not sender:
281 if not sender:
278 sender = self.ui.config('email', 'from') or self.ui.username()
282 sender = self.ui.config('email', 'from') or self.ui.username()
279 if '@' not in sender or '@localhost' in sender:
283 if '@' not in sender or '@localhost' in sender:
280 sender = self.fixmail(sender)
284 sender = self.fixmail(sender)
281 msg['From'] = mail.addressencode(self.ui, sender,
285 msg['From'] = mail.addressencode(self.ui, sender,
282 self.charsets, self.test)
286 self.charsets, self.test)
283
287
284 msg['X-Hg-Notification'] = 'changeset %s' % ctx
288 msg['X-Hg-Notification'] = 'changeset %s' % ctx
285 if not msg['Message-Id']:
289 if not msg['Message-Id']:
286 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
290 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
287 (ctx, int(time.time()),
291 (ctx, int(time.time()),
288 hash(self.repo.root), socket.getfqdn()))
292 hash(self.repo.root), socket.getfqdn()))
289 msg['To'] = ', '.join(self.subs)
293 msg['To'] = ', '.join(self.subs)
290
294
291 msgtext = msg.as_string()
295 msgtext = msg.as_string()
292 if self.test:
296 if self.test:
293 self.ui.write(msgtext)
297 self.ui.write(msgtext)
294 if not msgtext.endswith('\n'):
298 if not msgtext.endswith('\n'):
295 self.ui.write('\n')
299 self.ui.write('\n')
296 else:
300 else:
297 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
301 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
298 (len(self.subs), count))
302 (len(self.subs), count))
299 mail.sendmail(self.ui, util.email(msg['From']),
303 mail.sendmail(self.ui, util.email(msg['From']),
300 self.subs, msgtext, mbox=self.mbox)
304 self.subs, msgtext, mbox=self.mbox)
301
305
302 def diff(self, ctx, ref=None):
306 def diff(self, ctx, ref=None):
303
307
304 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
308 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
305 prev = ctx.p1().node()
309 prev = ctx.p1().node()
306 ref = ref and ref.node() or ctx.node()
310 ref = ref and ref.node() or ctx.node()
307 chunks = patch.diff(self.repo, prev, ref, opts=patch.diffopts(self.ui))
311 chunks = patch.diff(self.repo, prev, ref, opts=patch.diffopts(self.ui))
308 difflines = ''.join(chunks).splitlines()
312 difflines = ''.join(chunks).splitlines()
309
313
310 if self.ui.configbool('notify', 'diffstat', True):
314 if self.ui.configbool('notify', 'diffstat', True):
311 s = patch.diffstat(difflines)
315 s = patch.diffstat(difflines)
312 # s may be nil, don't include the header if it is
316 # s may be nil, don't include the header if it is
313 if s:
317 if s:
314 self.ui.write('\ndiffstat:\n\n%s' % s)
318 self.ui.write('\ndiffstat:\n\n%s' % s)
315
319
316 if maxdiff == 0:
320 if maxdiff == 0:
317 return
321 return
318 elif maxdiff > 0 and len(difflines) > maxdiff:
322 elif maxdiff > 0 and len(difflines) > maxdiff:
319 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
323 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
320 self.ui.write(msg % (len(difflines), maxdiff))
324 self.ui.write(msg % (len(difflines), maxdiff))
321 difflines = difflines[:maxdiff]
325 difflines = difflines[:maxdiff]
322 elif difflines:
326 elif difflines:
323 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
327 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
324
328
325 self.ui.write("\n".join(difflines))
329 self.ui.write("\n".join(difflines))
326
330
327 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
331 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
328 '''send email notifications to interested subscribers.
332 '''send email notifications to interested subscribers.
329
333
330 if used as changegroup hook, send one email for all changesets in
334 if used as changegroup hook, send one email for all changesets in
331 changegroup. else send one email per changeset.'''
335 changegroup. else send one email per changeset.'''
332
336
333 n = notifier(ui, repo, hooktype)
337 n = notifier(ui, repo, hooktype)
334 ctx = repo[node]
338 ctx = repo[node]
335
339
336 if not n.subs:
340 if not n.subs:
337 ui.debug('notify: no subscribers to repository %s\n' % n.root)
341 ui.debug('notify: no subscribers to repository %s\n' % n.root)
338 return
342 return
339 if n.skipsource(source):
343 if n.skipsource(source):
340 ui.debug('notify: changes have source "%s" - skipping\n' % source)
344 ui.debug('notify: changes have source "%s" - skipping\n' % source)
341 return
345 return
342
346
343 ui.pushbuffer()
347 ui.pushbuffer()
344 data = ''
348 data = ''
345 count = 0
349 count = 0
346 author = ''
350 author = ''
347 if hooktype == 'changegroup' or hooktype == 'outgoing':
351 if hooktype == 'changegroup' or hooktype == 'outgoing':
348 start, end = ctx.rev(), len(repo)
352 start, end = ctx.rev(), len(repo)
349 for rev in xrange(start, end):
353 for rev in xrange(start, end):
350 if n.node(repo[rev]):
354 if n.node(repo[rev]):
351 count += 1
355 count += 1
352 if not author:
356 if not author:
353 author = repo[rev].user()
357 author = repo[rev].user()
354 else:
358 else:
355 data += ui.popbuffer()
359 data += ui.popbuffer()
356 ui.note(_('notify: suppressing notification for merge %d:%s\n') %
360 ui.note(_('notify: suppressing notification for merge %d:%s\n') %
357 (rev, repo[rev].hex()[:12]))
361 (rev, repo[rev].hex()[:12]))
358 ui.pushbuffer()
362 ui.pushbuffer()
359 if count:
363 if count:
360 n.diff(ctx, repo['tip'])
364 n.diff(ctx, repo['tip'])
361 else:
365 else:
362 if not n.node(ctx):
366 if not n.node(ctx):
363 ui.popbuffer()
367 ui.popbuffer()
364 ui.note(_('notify: suppressing notification for merge %d:%s\n') %
368 ui.note(_('notify: suppressing notification for merge %d:%s\n') %
365 (ctx.rev(), ctx.hex()[:12]))
369 (ctx.rev(), ctx.hex()[:12]))
366 return
370 return
367 count += 1
371 count += 1
368 n.diff(ctx)
372 n.diff(ctx)
369
373
370 data += ui.popbuffer()
374 data += ui.popbuffer()
371 fromauthor = ui.config('notify', 'fromauthor')
375 fromauthor = ui.config('notify', 'fromauthor')
372 if author and fromauthor:
376 if author and fromauthor:
373 data = '\n'.join(['From: %s' % author, data])
377 data = '\n'.join(['From: %s' % author, data])
374
378
375 if count:
379 if count:
376 n.send(ctx, count, data)
380 n.send(ctx, count, data)
@@ -1,469 +1,475 b''
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 let you run hooks sending email notifications when changesets
22 This extension implements hooks to send email notifications when changesets
23 are being pushed, from the sending or receiving side.
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 by the
26 the hook you want to run. "incoming" and "changegroup" hooks are run when
27 changesets receiver while the "outgoing" one is for the sender:
27 changesets are received, while "outgoing" hooks are for changesets sent to
28 another repository:
28
29
29 [hooks]
30 [hooks]
30 # one email for each incoming changeset
31 # one email for each incoming changeset
31 incoming.notify = python:hgext.notify.hook
32 incoming.notify = python:hgext.notify.hook
32 # one email for all incoming changesets
33 # one email for all incoming changesets
33 changegroup.notify = python:hgext.notify.hook
34 changegroup.notify = python:hgext.notify.hook
34
35
35 # one email for all outgoing changesets
36 # one email for all outgoing changesets
36 outgoing.notify = python:hgext.notify.hook
37 outgoing.notify = python:hgext.notify.hook
37
38
38 Now the hooks are running, subscribers must be assigned to repositories. Use
39 This registers the hooks. To enable notification, subscribers must be assigned
39 the "[usersubs]" section to map repositories to a given email or the
40 to repositories. The "[usersubs]" section maps multiple repositories to a
40 "[reposubs]" section to map emails to a single repository:
41 given recipient. The "[reposubs]" section maps multiple recipients to a single
42 repository:
41
43
42 [usersubs]
44 [usersubs]
43 # key is subscriber email, value is a comma-separated list of glob
45 # key is subscriber email, value is a comma-separated list of repo glob
44 # patterns
46 # patterns
45 user@host = pattern
47 user@host = pattern
46
48
47 [reposubs]
49 [reposubs]
48 # key is glob pattern, value is a comma-separated list of subscriber
50 # key is glob pattern, value is a comma-separated list of subscriber
49 # emails
51 # emails
50 pattern = user@host
52 pattern = user@host
51
53
52 Glob patterns are matched against absolute path to repository root. The
54 Glob patterns are matched against absolute path to repository root.
53 subscriptions can be defined in their own file and referenced with:
55
56 In order to place them under direct user management, "[usersubs]" and
57 "[reposubs]" sections may be placed in a separate "hgrc" file and incorporated
58 by reference:
54
59
55 [notify]
60 [notify]
56 config = /path/to/subscriptionsfile
61 config = /path/to/subscriptionsfile
57
62
58 Alternatively, they can be added to Mercurial configuration files by setting
63 Notifications will not be sent until the "notify.test" value is set to
59 the previous entry to an empty value.
64 "False"; see below.
60
61 At this point, notifications should be generated but will not be sent until
62 you set the "notify.test" entry to "False".
63
65
64 Notifications content can be tweaked with the following configuration entries:
66 Notifications content can be tweaked with the following configuration entries:
65
67
66 notify.test
68 notify.test
67 If "True", print messages to stdout instead of sending them. Default: True.
69 If "True", print messages to stdout instead of sending them. Default: True.
68
70
69 notify.sources
71 notify.sources
70 Space separated list of change sources. Notifications are sent only if it
72 Space-separated list of change sources. Notifications are activated only
71 includes the incoming or outgoing changes source. Incoming sources can be
73 when a changeset's source is in this list. Sources may be:
72 "serve" for changes coming from http or ssh, "pull" for pulled changes,
74
73 "unbundle" for changes added by "hg unbundle" or "push" for changes being
75 "serve" changesets received via http or ssh
74 pushed locally. Outgoing sources are the same except for "unbundle" which is
76 "pull" changesets received via "hg pull"
75 replaced by "bundle". Default: serve.
77 "unbundle" changesets received via "hg unbundle"
78 "push" changesets sent or received via "hg push"
79 "bundle" changesets sent via "hg unbundle"
80
81 Default: serve.
76
82
77 notify.strip
83 notify.strip
78 Number of leading slashes to strip from url paths. By default, notifications
84 Number of leading slashes to strip from url paths. By default, notifications
79 references repositories with their absolute path. "notify.strip" let you
85 reference repositories with their absolute path. "notify.strip" lets you
80 turn them into relative paths. For example, "notify.strip=3" will change
86 turn them into relative paths. For example, "notify.strip=3" will change
81 "/long/path/repository" into "repository". Default: 0.
87 "/long/path/repository" into "repository". Default: 0.
82
88
83 notify.domain
89 notify.domain
84 If subscribers emails or the from email have no domain set, complete them
90 Default email domain for sender or recipients with no explicit domain.
85 with this value.
86
91
87 notify.style
92 notify.style
88 Style file to use when formatting emails.
93 Style file to use when formatting emails.
89
94
90 notify.template
95 notify.template
91 Template to use when formatting emails.
96 Template to use when formatting emails.
92
97
93 notify.incoming
98 notify.incoming
94 Template to use when run as incoming hook, override "notify.template".
99 Template to use when run as an incoming hook, overriding "notify.template".
95
100
96 notify.outgoing
101 notify.outgoing
97 Template to use when run as outgoing hook, override "notify.template".
102 Template to use when run as an outgoing hook, overriding "notify.template".
98
103
99 notify.changegroup
104 notify.changegroup
100 Template to use when running as changegroup hook, override
105 Template to use when running as a changegroup hook, overriding
101 "notify.template".
106 "notify.template".
102
107
103 notify.maxdiff
108 notify.maxdiff
104 Maximum number of diff lines to include in notification email. Set to 0 to
109 Maximum number of diff lines to include in notification email. Set to 0 to
105 disable the diff, -1 to include all of it. Default: 300.
110 disable the diff, or -1 to include all of it. Default: 300.
106
111
107 notify.maxsubject
112 notify.maxsubject
108 Maximum number of characters in emails subject line. Default: 67.
113 Maximum number of characters in email's subject line. Default: 67.
109
114
110 notify.diffstat
115 notify.diffstat
111 Set to True to include a diffstat before diff content. Default: True.
116 Set to True to include a diffstat before diff content. Default: True.
112
117
113 notify.merge
118 notify.merge
114 If True, send notifications for merge changesets. Default: True.
119 If True, send notifications for merge changesets. Default: True.
115
120
116 notify.mbox
121 notify.mbox
117 If set, append mails to this mbox file instead of sending. Default: None.
122 If set, append mails to this mbox file instead of sending. Default: None.
118
123
119 notify.fromauthor
124 notify.fromauthor
120 If set, use the first committer of the changegroup for the "From" field of
125 If set, use the committer of the first changeset in a changegroup for the
121 the notification mail. If not set, take the user from the pushing repo.
126 "From" field of the notification mail. If not set, take the user from the
122 Default: False.
127 pushing repo. Default: False.
123
128
124 If set, the following entries will also be used to customize the
129 If set, the following entries will also be used to customize the
125 notifications:
130 notifications:
126
131
127 email.from
132 email.from
128 Email "From" address to use if none can be found in generated email content.
133 Email "From" address to use if none can be found in the generated email
134 content.
129
135
130 web.baseurl
136 web.baseurl
131 Root repository browsing URL to combine with repository paths when making
137 Root repository URL to combine with repository paths when making references.
132 references. See also "notify.strip".
138 See also "notify.strip".
133
139
134 no commands defined
140 no commands defined
135 $ hg init a
141 $ hg init a
136 $ echo a > a/a
142 $ echo a > a/a
137
143
138 commit
144 commit
139
145
140 $ hg --cwd a commit -Ama -d '0 0'
146 $ hg --cwd a commit -Ama -d '0 0'
141 adding a
147 adding a
142
148
143
149
144 clone
150 clone
145
151
146 $ hg --traceback clone a b
152 $ hg --traceback clone a b
147 updating to branch default
153 updating to branch default
148 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
154 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
149 $ echo a >> a/a
155 $ echo a >> a/a
150
156
151 commit
157 commit
152
158
153 $ hg --traceback --cwd a commit -Amb -d '1 0'
159 $ hg --traceback --cwd a commit -Amb -d '1 0'
154
160
155 on Mac OS X 10.5 the tmp path is very long so would get stripped in the subject line
161 on Mac OS X 10.5 the tmp path is very long so would get stripped in the subject line
156
162
157 $ cat <<EOF >> $HGRCPATH
163 $ cat <<EOF >> $HGRCPATH
158 > [notify]
164 > [notify]
159 > maxsubject = 200
165 > maxsubject = 200
160 > EOF
166 > EOF
161
167
162 the python call below wraps continuation lines, which appear on Mac OS X 10.5 because
168 the python call below wraps continuation lines, which appear on Mac OS X 10.5 because
163 of the very long subject line
169 of the very long subject line
164 pull (minimal config)
170 pull (minimal config)
165
171
166 $ hg --traceback --cwd b pull ../a | \
172 $ hg --traceback --cwd b pull ../a | \
167 > python -c 'import sys,re; print re.sub("\n[\t ]", " ", sys.stdin.read()),'
173 > python -c 'import sys,re; print re.sub("\n[\t ]", " ", sys.stdin.read()),'
168 pulling from ../a
174 pulling from ../a
169 searching for changes
175 searching for changes
170 adding changesets
176 adding changesets
171 adding manifests
177 adding manifests
172 adding file changes
178 adding file changes
173 added 1 changesets with 1 changes to 1 files
179 added 1 changesets with 1 changes to 1 files
174 Content-Type: text/plain; charset="us-ascii"
180 Content-Type: text/plain; charset="us-ascii"
175 MIME-Version: 1.0
181 MIME-Version: 1.0
176 Content-Transfer-Encoding: 7bit
182 Content-Transfer-Encoding: 7bit
177 Date: * (glob)
183 Date: * (glob)
178 Subject: changeset in $TESTTMP/b: b
184 Subject: changeset in $TESTTMP/b: b
179 From: test
185 From: test
180 X-Hg-Notification: changeset 0647d048b600
186 X-Hg-Notification: changeset 0647d048b600
181 Message-Id: <*> (glob)
187 Message-Id: <*> (glob)
182 To: baz, foo@bar
188 To: baz, foo@bar
183
189
184 changeset 0647d048b600 in $TESTTMP/b (glob)
190 changeset 0647d048b600 in $TESTTMP/b (glob)
185 details: $TESTTMP/b?cmd=changeset;node=0647d048b600
191 details: $TESTTMP/b?cmd=changeset;node=0647d048b600
186 description: b
192 description: b
187
193
188 diffs (6 lines):
194 diffs (6 lines):
189
195
190 diff -r cb9a9f314b8b -r 0647d048b600 a
196 diff -r cb9a9f314b8b -r 0647d048b600 a
191 --- a/a Thu Jan 01 00:00:00 1970 +0000
197 --- a/a Thu Jan 01 00:00:00 1970 +0000
192 +++ b/a Thu Jan 01 00:00:01 1970 +0000
198 +++ b/a Thu Jan 01 00:00:01 1970 +0000
193 @@ -1,1 +1,2 @@ a
199 @@ -1,1 +1,2 @@ a
194 +a
200 +a
195 (run 'hg update' to get a working copy)
201 (run 'hg update' to get a working copy)
196 $ cat <<EOF >> $HGRCPATH
202 $ cat <<EOF >> $HGRCPATH
197 > [notify]
203 > [notify]
198 > config = `pwd`/.notify.conf
204 > config = `pwd`/.notify.conf
199 > domain = test.com
205 > domain = test.com
200 > strip = 42
206 > strip = 42
201 > template = Subject: {desc|firstline|strip}\nFrom: {author}\nX-Test: foo\n\nchangeset {node|short} in {webroot}\ndescription:\n\t{desc|tabindent|strip}
207 > template = Subject: {desc|firstline|strip}\nFrom: {author}\nX-Test: foo\n\nchangeset {node|short} in {webroot}\ndescription:\n\t{desc|tabindent|strip}
202 >
208 >
203 > [web]
209 > [web]
204 > baseurl = http://test/
210 > baseurl = http://test/
205 > EOF
211 > EOF
206
212
207 fail for config file is missing
213 fail for config file is missing
208
214
209 $ hg --cwd b rollback
215 $ hg --cwd b rollback
210 repository tip rolled back to revision 0 (undo pull)
216 repository tip rolled back to revision 0 (undo pull)
211 $ hg --cwd b pull ../a 2>&1 | grep 'error.*\.notify\.conf' > /dev/null && echo pull failed
217 $ hg --cwd b pull ../a 2>&1 | grep 'error.*\.notify\.conf' > /dev/null && echo pull failed
212 pull failed
218 pull failed
213 $ touch ".notify.conf"
219 $ touch ".notify.conf"
214
220
215 pull
221 pull
216
222
217 $ hg --cwd b rollback
223 $ hg --cwd b rollback
218 repository tip rolled back to revision 0 (undo pull)
224 repository tip rolled back to revision 0 (undo pull)
219 $ hg --traceback --cwd b pull ../a | \
225 $ hg --traceback --cwd b pull ../a | \
220 > python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
226 > python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
221 pulling from ../a
227 pulling from ../a
222 searching for changes
228 searching for changes
223 adding changesets
229 adding changesets
224 adding manifests
230 adding manifests
225 adding file changes
231 adding file changes
226 added 1 changesets with 1 changes to 1 files
232 added 1 changesets with 1 changes to 1 files
227 Content-Type: text/plain; charset="us-ascii"
233 Content-Type: text/plain; charset="us-ascii"
228 MIME-Version: 1.0
234 MIME-Version: 1.0
229 Content-Transfer-Encoding: 7bit
235 Content-Transfer-Encoding: 7bit
230 X-Test: foo
236 X-Test: foo
231 Date: * (glob)
237 Date: * (glob)
232 Subject: b
238 Subject: b
233 From: test@test.com
239 From: test@test.com
234 X-Hg-Notification: changeset 0647d048b600
240 X-Hg-Notification: changeset 0647d048b600
235 Message-Id: <*> (glob)
241 Message-Id: <*> (glob)
236 To: baz@test.com, foo@bar
242 To: baz@test.com, foo@bar
237
243
238 changeset 0647d048b600 in b
244 changeset 0647d048b600 in b
239 description: b
245 description: b
240 diffs (6 lines):
246 diffs (6 lines):
241
247
242 diff -r cb9a9f314b8b -r 0647d048b600 a
248 diff -r cb9a9f314b8b -r 0647d048b600 a
243 --- a/a Thu Jan 01 00:00:00 1970 +0000
249 --- a/a Thu Jan 01 00:00:00 1970 +0000
244 +++ b/a Thu Jan 01 00:00:01 1970 +0000
250 +++ b/a Thu Jan 01 00:00:01 1970 +0000
245 @@ -1,1 +1,2 @@
251 @@ -1,1 +1,2 @@
246 a
252 a
247 +a
253 +a
248 (run 'hg update' to get a working copy)
254 (run 'hg update' to get a working copy)
249
255
250 $ cat << EOF >> $HGRCPATH
256 $ cat << EOF >> $HGRCPATH
251 > [hooks]
257 > [hooks]
252 > incoming.notify = python:hgext.notify.hook
258 > incoming.notify = python:hgext.notify.hook
253 >
259 >
254 > [notify]
260 > [notify]
255 > sources = pull
261 > sources = pull
256 > diffstat = True
262 > diffstat = True
257 > EOF
263 > EOF
258
264
259 pull
265 pull
260
266
261 $ hg --cwd b rollback
267 $ hg --cwd b rollback
262 repository tip rolled back to revision 0 (undo pull)
268 repository tip rolled back to revision 0 (undo pull)
263 $ hg --traceback --cwd b pull ../a | \
269 $ hg --traceback --cwd b pull ../a | \
264 > python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
270 > python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
265 pulling from ../a
271 pulling from ../a
266 searching for changes
272 searching for changes
267 adding changesets
273 adding changesets
268 adding manifests
274 adding manifests
269 adding file changes
275 adding file changes
270 added 1 changesets with 1 changes to 1 files
276 added 1 changesets with 1 changes to 1 files
271 Content-Type: text/plain; charset="us-ascii"
277 Content-Type: text/plain; charset="us-ascii"
272 MIME-Version: 1.0
278 MIME-Version: 1.0
273 Content-Transfer-Encoding: 7bit
279 Content-Transfer-Encoding: 7bit
274 X-Test: foo
280 X-Test: foo
275 Date: * (glob)
281 Date: * (glob)
276 Subject: b
282 Subject: b
277 From: test@test.com
283 From: test@test.com
278 X-Hg-Notification: changeset 0647d048b600
284 X-Hg-Notification: changeset 0647d048b600
279 Message-Id: <*> (glob)
285 Message-Id: <*> (glob)
280 To: baz@test.com, foo@bar
286 To: baz@test.com, foo@bar
281
287
282 changeset 0647d048b600 in b
288 changeset 0647d048b600 in b
283 description: b
289 description: b
284 diffstat:
290 diffstat:
285
291
286 a | 1 +
292 a | 1 +
287 1 files changed, 1 insertions(+), 0 deletions(-)
293 1 files changed, 1 insertions(+), 0 deletions(-)
288
294
289 diffs (6 lines):
295 diffs (6 lines):
290
296
291 diff -r cb9a9f314b8b -r 0647d048b600 a
297 diff -r cb9a9f314b8b -r 0647d048b600 a
292 --- a/a Thu Jan 01 00:00:00 1970 +0000
298 --- a/a Thu Jan 01 00:00:00 1970 +0000
293 +++ b/a Thu Jan 01 00:00:01 1970 +0000
299 +++ b/a Thu Jan 01 00:00:01 1970 +0000
294 @@ -1,1 +1,2 @@
300 @@ -1,1 +1,2 @@
295 a
301 a
296 +a
302 +a
297 (run 'hg update' to get a working copy)
303 (run 'hg update' to get a working copy)
298
304
299 test merge
305 test merge
300
306
301 $ cd a
307 $ cd a
302 $ hg up -C 0
308 $ hg up -C 0
303 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
309 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
304 $ echo a >> a
310 $ echo a >> a
305 $ hg ci -Am adda2 -d '2 0'
311 $ hg ci -Am adda2 -d '2 0'
306 created new head
312 created new head
307 $ hg merge
313 $ hg merge
308 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
314 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
309 (branch merge, don't forget to commit)
315 (branch merge, don't forget to commit)
310 $ hg ci -m merge -d '3 0'
316 $ hg ci -m merge -d '3 0'
311 $ cd ..
317 $ cd ..
312 $ hg --traceback --cwd b pull ../a | \
318 $ hg --traceback --cwd b pull ../a | \
313 > python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
319 > python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
314 pulling from ../a
320 pulling from ../a
315 searching for changes
321 searching for changes
316 adding changesets
322 adding changesets
317 adding manifests
323 adding manifests
318 adding file changes
324 adding file changes
319 added 2 changesets with 0 changes to 0 files
325 added 2 changesets with 0 changes to 0 files
320 Content-Type: text/plain; charset="us-ascii"
326 Content-Type: text/plain; charset="us-ascii"
321 MIME-Version: 1.0
327 MIME-Version: 1.0
322 Content-Transfer-Encoding: 7bit
328 Content-Transfer-Encoding: 7bit
323 X-Test: foo
329 X-Test: foo
324 Date: * (glob)
330 Date: * (glob)
325 Subject: adda2
331 Subject: adda2
326 From: test@test.com
332 From: test@test.com
327 X-Hg-Notification: changeset 0a184ce6067f
333 X-Hg-Notification: changeset 0a184ce6067f
328 Message-Id: <*> (glob)
334 Message-Id: <*> (glob)
329 To: baz@test.com, foo@bar
335 To: baz@test.com, foo@bar
330
336
331 changeset 0a184ce6067f in b
337 changeset 0a184ce6067f in b
332 description: adda2
338 description: adda2
333 diffstat:
339 diffstat:
334
340
335 a | 1 +
341 a | 1 +
336 1 files changed, 1 insertions(+), 0 deletions(-)
342 1 files changed, 1 insertions(+), 0 deletions(-)
337
343
338 diffs (6 lines):
344 diffs (6 lines):
339
345
340 diff -r cb9a9f314b8b -r 0a184ce6067f a
346 diff -r cb9a9f314b8b -r 0a184ce6067f a
341 --- a/a Thu Jan 01 00:00:00 1970 +0000
347 --- a/a Thu Jan 01 00:00:00 1970 +0000
342 +++ b/a Thu Jan 01 00:00:02 1970 +0000
348 +++ b/a Thu Jan 01 00:00:02 1970 +0000
343 @@ -1,1 +1,2 @@
349 @@ -1,1 +1,2 @@
344 a
350 a
345 +a
351 +a
346 Content-Type: text/plain; charset="us-ascii"
352 Content-Type: text/plain; charset="us-ascii"
347 MIME-Version: 1.0
353 MIME-Version: 1.0
348 Content-Transfer-Encoding: 7bit
354 Content-Transfer-Encoding: 7bit
349 X-Test: foo
355 X-Test: foo
350 Date: * (glob)
356 Date: * (glob)
351 Subject: merge
357 Subject: merge
352 From: test@test.com
358 From: test@test.com
353 X-Hg-Notification: changeset 6a0cf76b2701
359 X-Hg-Notification: changeset 6a0cf76b2701
354 Message-Id: <*> (glob)
360 Message-Id: <*> (glob)
355 To: baz@test.com, foo@bar
361 To: baz@test.com, foo@bar
356
362
357 changeset 6a0cf76b2701 in b
363 changeset 6a0cf76b2701 in b
358 description: merge
364 description: merge
359 (run 'hg update' to get a working copy)
365 (run 'hg update' to get a working copy)
360
366
361 non-ascii content and truncation of multi-byte subject
367 non-ascii content and truncation of multi-byte subject
362
368
363 $ cat <<EOF >> $HGRCPATH
369 $ cat <<EOF >> $HGRCPATH
364 > [notify]
370 > [notify]
365 > maxsubject = 4
371 > maxsubject = 4
366 > EOF
372 > EOF
367 $ echo a >> a/a
373 $ echo a >> a/a
368 $ hg --cwd a --encoding utf-8 commit -A -d '0 0' \
374 $ hg --cwd a --encoding utf-8 commit -A -d '0 0' \
369 > -m `python -c 'print "\xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4"'`
375 > -m `python -c 'print "\xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4"'`
370 $ hg --traceback --cwd b --encoding utf-8 pull ../a | \
376 $ hg --traceback --cwd b --encoding utf-8 pull ../a | \
371 > python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
377 > python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
372 pulling from ../a
378 pulling from ../a
373 searching for changes
379 searching for changes
374 adding changesets
380 adding changesets
375 adding manifests
381 adding manifests
376 adding file changes
382 adding file changes
377 added 1 changesets with 1 changes to 1 files
383 added 1 changesets with 1 changes to 1 files
378 Content-Type: text/plain; charset="us-ascii"
384 Content-Type: text/plain; charset="us-ascii"
379 MIME-Version: 1.0
385 MIME-Version: 1.0
380 Content-Transfer-Encoding: 8bit
386 Content-Transfer-Encoding: 8bit
381 X-Test: foo
387 X-Test: foo
382 Date: * (glob)
388 Date: * (glob)
383 Subject: \xc3\xa0... (esc)
389 Subject: \xc3\xa0... (esc)
384 From: test@test.com
390 From: test@test.com
385 X-Hg-Notification: changeset 7ea05ad269dc
391 X-Hg-Notification: changeset 7ea05ad269dc
386 Message-Id: <*> (glob)
392 Message-Id: <*> (glob)
387 To: baz@test.com, foo@bar
393 To: baz@test.com, foo@bar
388
394
389 changeset 7ea05ad269dc in b
395 changeset 7ea05ad269dc in b
390 description: \xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4 (esc)
396 description: \xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4 (esc)
391 diffstat:
397 diffstat:
392
398
393 a | 1 +
399 a | 1 +
394 1 files changed, 1 insertions(+), 0 deletions(-)
400 1 files changed, 1 insertions(+), 0 deletions(-)
395
401
396 diffs (7 lines):
402 diffs (7 lines):
397
403
398 diff -r 6a0cf76b2701 -r 7ea05ad269dc a
404 diff -r 6a0cf76b2701 -r 7ea05ad269dc a
399 --- a/a Thu Jan 01 00:00:03 1970 +0000
405 --- a/a Thu Jan 01 00:00:03 1970 +0000
400 +++ b/a Thu Jan 01 00:00:00 1970 +0000
406 +++ b/a Thu Jan 01 00:00:00 1970 +0000
401 @@ -1,2 +1,3 @@
407 @@ -1,2 +1,3 @@
402 a
408 a
403 a
409 a
404 +a
410 +a
405 (run 'hg update' to get a working copy)
411 (run 'hg update' to get a working copy)
406
412
407 long lines
413 long lines
408
414
409 $ cat <<EOF >> $HGRCPATH
415 $ cat <<EOF >> $HGRCPATH
410 > [notify]
416 > [notify]
411 > maxsubject = 67
417 > maxsubject = 67
412 > test = False
418 > test = False
413 > mbox = mbox
419 > mbox = mbox
414 > EOF
420 > EOF
415 $ python -c 'file("a/a", "ab").write("no" * 500 + "\n")'
421 $ python -c 'file("a/a", "ab").write("no" * 500 + "\n")'
416 $ hg --cwd a commit -A -m "long line"
422 $ hg --cwd a commit -A -m "long line"
417 $ hg --traceback --cwd b pull ../a
423 $ hg --traceback --cwd b pull ../a
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 notify: sending 2 subscribers 1 changes
430 notify: sending 2 subscribers 1 changes
425 (run 'hg update' to get a working copy)
431 (run 'hg update' to get a working copy)
426 $ python -c 'import sys,re; print re.sub("\n\t", " ", file("b/mbox").read()),'
432 $ python -c 'import sys,re; print re.sub("\n\t", " ", file("b/mbox").read()),'
427 From test@test.com ... ... .. ..:..:.. .... (re)
433 From test@test.com ... ... .. ..:..:.. .... (re)
428 Content-Type: text/plain; charset="us-ascii"
434 Content-Type: text/plain; charset="us-ascii"
429 MIME-Version: 1.0
435 MIME-Version: 1.0
430 Content-Transfer-Encoding: quoted-printable
436 Content-Transfer-Encoding: quoted-printable
431 X-Test: foo
437 X-Test: foo
432 Date: * (glob)
438 Date: * (glob)
433 Subject: long line
439 Subject: long line
434 From: test@test.com
440 From: test@test.com
435 X-Hg-Notification: changeset e0be44cf638b
441 X-Hg-Notification: changeset e0be44cf638b
436 Message-Id: <hg.e0be44cf638b.*.*@*> (glob)
442 Message-Id: <hg.e0be44cf638b.*.*@*> (glob)
437 To: baz@test.com, foo@bar
443 To: baz@test.com, foo@bar
438
444
439 changeset e0be44cf638b in b
445 changeset e0be44cf638b in b
440 description: long line
446 description: long line
441 diffstat:
447 diffstat:
442
448
443 a | 1 +
449 a | 1 +
444 1 files changed, 1 insertions(+), 0 deletions(-)
450 1 files changed, 1 insertions(+), 0 deletions(-)
445
451
446 diffs (8 lines):
452 diffs (8 lines):
447
453
448 diff -r 7ea05ad269dc -r e0be44cf638b a
454 diff -r 7ea05ad269dc -r e0be44cf638b a
449 --- a/a Thu Jan 01 00:00:00 1970 +0000
455 --- a/a Thu Jan 01 00:00:00 1970 +0000
450 +++ b/a Thu Jan 01 00:00:00 1970 +0000
456 +++ b/a Thu Jan 01 00:00:00 1970 +0000
451 @@ -1,3 +1,4 @@
457 @@ -1,3 +1,4 @@
452 a
458 a
453 a
459 a
454 a
460 a
455 +nonononononononononononononononononononononononononononononononononononono=
461 +nonononononononononononononononononononononononononononononononononononono=
456 nononononononononononononononononononononononononononononononononononononon=
462 nononononononononononononononononononononononononononononononononononononon=
457 ononononononononononononononononononononononononononononononononononononono=
463 ononononononononononononononononononononononononononononononononononononono=
458 nononononononononononononononononononononononononononononononononononononon=
464 nononononononononononononononononononononononononononononononononononononon=
459 ononononononononononononononononononononononononononononononononononononono=
465 ononononononononononononononononononononononononononononononononononononono=
460 nononononononononononononononononononononononononononononononononononononon=
466 nononononononononononononononononononononononononononononononononononononon=
461 ononononononononononononononononononononononononononononononononononononono=
467 ononononononononononononononononononononononononononononononononononononono=
462 nononononononononononononononononononononononononononononononononononononon=
468 nononononononononononononononononononononononononononononononononononononon=
463 ononononononononononononononononononononononononononononononononononononono=
469 ononononononononononononononononononononononononononononononononononononono=
464 nononononononononononononononononononononononononononononononononononononon=
470 nononononononononononononononononononononononononononononononononononononon=
465 ononononononononononononononononononononononononononononononononononononono=
471 ononononononononononononononononononononononononononononononononononononono=
466 nononononononononononononononononononononononononononononononononononononon=
472 nononononononononononononononononononononononononononononononononononononon=
467 ononononononononononononononononononononononononononononononononononononono=
473 ononononononononononononononononononononononononononononononononononononono=
468 nonononononononononononono
474 nonononononononononononono
469
475
General Comments 0
You need to be logged in to leave comments. Login now