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