##// END OF EJS Templates
notify: send changesets on 'outgoing' hook, updated doc
Ingo Bressler -
r14617:23f4e1e4 default
parent child Browse files
Show More
@@ -1,316 +1,319
1 1 # notify.py - email notifications for mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''hooks for sending email notifications at commit/push time
9 9
10 10 Subscriptions can be managed through a hgrc file. Default mode is to
11 11 print messages to stdout, for testing and configuring.
12 12
13 13 To use, configure the notify extension and enable it in hgrc like
14 14 this::
15 15
16 16 [extensions]
17 17 notify =
18 18
19 19 [hooks]
20 20 # one email for each incoming changeset
21 21 incoming.notify = python:hgext.notify.hook
22 22 # batch emails when many changesets incoming at one time
23 23 changegroup.notify = python:hgext.notify.hook
24 # batch emails when many changesets outgoing at one time (client side)
25 outgoing.notify = python:hgext.notify.hook
24 26
25 27 [notify]
26 28 # config items go here
27 29
28 30 Required configuration items::
29 31
30 32 config = /path/to/file # file containing subscriptions
31 33
32 34 Optional configuration items::
33 35
34 36 test = True # print messages to stdout for testing
35 37 strip = 3 # number of slashes to strip for url paths
36 38 domain = example.com # domain to use if committer missing domain
37 39 style = ... # style file to use when formatting email
38 40 template = ... # template to use when formatting email
39 41 incoming = ... # template to use when run as incoming hook
40 changegroup = ... # template when run as changegroup hook
42 outgoing = ... # template to use when run as outgoing hook
43 changegroup = ... # template to use when run as changegroup hook
41 44 maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
42 45 maxsubject = 67 # truncate subject line longer than this
43 46 diffstat = True # add a diffstat before the diff content
44 47 sources = serve # notify if source of incoming changes in this list
45 48 # (serve == ssh or http, push, pull, bundle)
46 49 merge = False # send notification for merges (default True)
47 50 [email]
48 51 from = user@host.com # email address to send as if none given
49 52 [web]
50 53 baseurl = http://hgserver/... # root of hg web site for browsing commits
51 54
52 55 The notify config file has same format as a regular hgrc file. It has
53 56 two sections so you can express subscriptions in whatever way is
54 57 handier for you.
55 58
56 59 ::
57 60
58 61 [usersubs]
59 62 # key is subscriber email, value is ","-separated list of glob patterns
60 63 user@host = pattern
61 64
62 65 [reposubs]
63 66 # key is glob pattern, value is ","-separated list of subscriber emails
64 67 pattern = user@host
65 68
66 69 Glob patterns are matched against path to repository root.
67 70
68 71 If you like, you can put notify config file in repository that users
69 72 can push changes to, they can manage their own subscriptions.
70 73 '''
71 74
72 75 from mercurial.i18n import _
73 76 from mercurial import patch, cmdutil, templater, util, mail
74 77 import email.Parser, email.Errors, fnmatch, socket, time
75 78
76 79 # template for single changeset can include email headers.
77 80 single_template = '''
78 81 Subject: changeset in {webroot}: {desc|firstline|strip}
79 82 From: {author}
80 83
81 84 changeset {node|short} in {root}
82 85 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
83 86 description:
84 87 \t{desc|tabindent|strip}
85 88 '''.lstrip()
86 89
87 90 # template for multiple changesets should not contain email headers,
88 91 # because only first set of headers will be used and result will look
89 92 # strange.
90 93 multiple_template = '''
91 94 changeset {node|short} in {root}
92 95 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
93 96 summary: {desc|firstline}
94 97 '''
95 98
96 99 deftemplates = {
97 100 'changegroup': multiple_template,
98 101 }
99 102
100 103 class notifier(object):
101 104 '''email notification class.'''
102 105
103 106 def __init__(self, ui, repo, hooktype):
104 107 self.ui = ui
105 108 cfg = self.ui.config('notify', 'config')
106 109 if cfg:
107 110 self.ui.readconfig(cfg, sections=['usersubs', 'reposubs'])
108 111 self.repo = repo
109 112 self.stripcount = int(self.ui.config('notify', 'strip', 0))
110 113 self.root = self.strip(self.repo.root)
111 114 self.domain = self.ui.config('notify', 'domain')
112 115 self.test = self.ui.configbool('notify', 'test', True)
113 116 self.charsets = mail._charsets(self.ui)
114 117 self.subs = self.subscribers()
115 118 self.merge = self.ui.configbool('notify', 'merge', True)
116 119
117 120 mapfile = self.ui.config('notify', 'style')
118 121 template = (self.ui.config('notify', hooktype) or
119 122 self.ui.config('notify', 'template'))
120 123 self.t = cmdutil.changeset_templater(self.ui, self.repo,
121 124 False, None, mapfile, False)
122 125 if not mapfile and not template:
123 126 template = deftemplates.get(hooktype) or single_template
124 127 if template:
125 128 template = templater.parsestring(template, quoted=False)
126 129 self.t.use_template(template)
127 130
128 131 def strip(self, path):
129 132 '''strip leading slashes from local path, turn into web-safe path.'''
130 133
131 134 path = util.pconvert(path)
132 135 count = self.stripcount
133 136 while count > 0:
134 137 c = path.find('/')
135 138 if c == -1:
136 139 break
137 140 path = path[c + 1:]
138 141 count -= 1
139 142 return path
140 143
141 144 def fixmail(self, addr):
142 145 '''try to clean up email addresses.'''
143 146
144 147 addr = util.email(addr.strip())
145 148 if self.domain:
146 149 a = addr.find('@localhost')
147 150 if a != -1:
148 151 addr = addr[:a]
149 152 if '@' not in addr:
150 153 return addr + '@' + self.domain
151 154 return addr
152 155
153 156 def subscribers(self):
154 157 '''return list of email addresses of subscribers to this repo.'''
155 158 subs = set()
156 159 for user, pats in self.ui.configitems('usersubs'):
157 160 for pat in pats.split(','):
158 161 if fnmatch.fnmatch(self.repo.root, pat.strip()):
159 162 subs.add(self.fixmail(user))
160 163 for pat, users in self.ui.configitems('reposubs'):
161 164 if fnmatch.fnmatch(self.repo.root, pat):
162 165 for user in users.split(','):
163 166 subs.add(self.fixmail(user))
164 167 return [mail.addressencode(self.ui, s, self.charsets, self.test)
165 168 for s in sorted(subs)]
166 169
167 170 def url(self, path=None):
168 171 return self.ui.config('web', 'baseurl') + (path or self.root)
169 172
170 173 def node(self, ctx, **props):
171 174 '''format one changeset, unless it is a suppressed merge.'''
172 175 if not self.merge and len(ctx.parents()) > 1:
173 176 return False
174 177 self.t.show(ctx, changes=ctx.changeset(),
175 178 baseurl=self.ui.config('web', 'baseurl'),
176 179 root=self.repo.root, webroot=self.root, **props)
177 180 return True
178 181
179 182 def skipsource(self, source):
180 183 '''true if incoming changes from this source should be skipped.'''
181 184 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
182 185 return source not in ok_sources
183 186
184 187 def send(self, ctx, count, data):
185 188 '''send message.'''
186 189
187 190 p = email.Parser.Parser()
188 191 try:
189 192 msg = p.parsestr(data)
190 193 except email.Errors.MessageParseError, inst:
191 194 raise util.Abort(inst)
192 195
193 196 # store sender and subject
194 197 sender, subject = msg['From'], msg['Subject']
195 198 del msg['From'], msg['Subject']
196 199
197 200 if not msg.is_multipart():
198 201 # create fresh mime message from scratch
199 202 # (multipart templates must take care of this themselves)
200 203 headers = msg.items()
201 204 payload = msg.get_payload()
202 205 # for notification prefer readability over data precision
203 206 msg = mail.mimeencode(self.ui, payload, self.charsets, self.test)
204 207 # reinstate custom headers
205 208 for k, v in headers:
206 209 msg[k] = v
207 210
208 211 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
209 212
210 213 # try to make subject line exist and be useful
211 214 if not subject:
212 215 if count > 1:
213 216 subject = _('%s: %d new changesets') % (self.root, count)
214 217 else:
215 218 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
216 219 subject = '%s: %s' % (self.root, s)
217 220 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
218 221 if maxsubject:
219 222 subject = util.ellipsis(subject, maxsubject)
220 223 msg['Subject'] = mail.headencode(self.ui, subject,
221 224 self.charsets, self.test)
222 225
223 226 # try to make message have proper sender
224 227 if not sender:
225 228 sender = self.ui.config('email', 'from') or self.ui.username()
226 229 if '@' not in sender or '@localhost' in sender:
227 230 sender = self.fixmail(sender)
228 231 msg['From'] = mail.addressencode(self.ui, sender,
229 232 self.charsets, self.test)
230 233
231 234 msg['X-Hg-Notification'] = 'changeset %s' % ctx
232 235 if not msg['Message-Id']:
233 236 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
234 237 (ctx, int(time.time()),
235 238 hash(self.repo.root), socket.getfqdn()))
236 239 msg['To'] = ', '.join(self.subs)
237 240
238 241 msgtext = msg.as_string()
239 242 if self.test:
240 243 self.ui.write(msgtext)
241 244 if not msgtext.endswith('\n'):
242 245 self.ui.write('\n')
243 246 else:
244 247 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
245 248 (len(self.subs), count))
246 249 mail.sendmail(self.ui, util.email(msg['From']),
247 250 self.subs, msgtext)
248 251
249 252 def diff(self, ctx, ref=None):
250 253
251 254 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
252 255 prev = ctx.p1().node()
253 256 ref = ref and ref.node() or ctx.node()
254 257 chunks = patch.diff(self.repo, prev, ref, opts=patch.diffopts(self.ui))
255 258 difflines = ''.join(chunks).splitlines()
256 259
257 260 if self.ui.configbool('notify', 'diffstat', True):
258 261 s = patch.diffstat(difflines)
259 262 # s may be nil, don't include the header if it is
260 263 if s:
261 264 self.ui.write('\ndiffstat:\n\n%s' % s)
262 265
263 266 if maxdiff == 0:
264 267 return
265 268 elif maxdiff > 0 and len(difflines) > maxdiff:
266 269 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
267 270 self.ui.write(msg % (len(difflines), maxdiff))
268 271 difflines = difflines[:maxdiff]
269 272 elif difflines:
270 273 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
271 274
272 275 self.ui.write("\n".join(difflines))
273 276
274 277 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
275 278 '''send email notifications to interested subscribers.
276 279
277 280 if used as changegroup hook, send one email for all changesets in
278 281 changegroup. else send one email per changeset.'''
279 282
280 283 n = notifier(ui, repo, hooktype)
281 284 ctx = repo[node]
282 285
283 286 if not n.subs:
284 287 ui.debug('notify: no subscribers to repository %s\n' % n.root)
285 288 return
286 289 if n.skipsource(source):
287 290 ui.debug('notify: changes have source "%s" - skipping\n' % source)
288 291 return
289 292
290 293 ui.pushbuffer()
291 294 data = ''
292 295 count = 0
293 if hooktype == 'changegroup':
296 if hooktype == 'changegroup' or hooktype == 'outgoing':
294 297 start, end = ctx.rev(), len(repo)
295 298 for rev in xrange(start, end):
296 299 if n.node(repo[rev]):
297 300 count += 1
298 301 else:
299 302 data += ui.popbuffer()
300 303 ui.note(_('notify: suppressing notification for merge %d:%s\n') %
301 304 (rev, repo[rev].hex()[:12]))
302 305 ui.pushbuffer()
303 306 if count:
304 307 n.diff(ctx, repo['tip'])
305 308 else:
306 309 if not n.node(ctx):
307 310 ui.popbuffer()
308 311 ui.note(_('notify: suppressing notification for merge %d:%s\n') %
309 312 (ctx.rev(), ctx.hex()[:12]))
310 313 return
311 314 count += 1
312 315 n.diff(ctx)
313 316
314 317 data += ui.popbuffer()
315 318 if count:
316 319 n.send(ctx, count, data)
@@ -1,353 +1,356
1 1
2 2 $ cat <<EOF >> $HGRCPATH
3 3 > [extensions]
4 4 > notify=
5 5 >
6 6 > [hooks]
7 7 > incoming.notify = python:hgext.notify.hook
8 8 >
9 9 > [notify]
10 10 > sources = pull
11 11 > diffstat = False
12 12 >
13 13 > [usersubs]
14 14 > foo@bar = *
15 15 >
16 16 > [reposubs]
17 17 > * = baz
18 18 > EOF
19 19 $ hg help notify
20 20 notify extension - hooks for sending email notifications at commit/push time
21 21
22 22 Subscriptions can be managed through a hgrc file. Default mode is to print
23 23 messages to stdout, for testing and configuring.
24 24
25 25 To use, configure the notify extension and enable it in hgrc like this:
26 26
27 27 [extensions]
28 28 notify =
29 29
30 30 [hooks]
31 31 # one email for each incoming changeset
32 32 incoming.notify = python:hgext.notify.hook
33 33 # batch emails when many changesets incoming at one time
34 34 changegroup.notify = python:hgext.notify.hook
35 # batch emails when many changesets outgoing at one time (client side)
36 outgoing.notify = python:hgext.notify.hook
35 37
36 38 [notify]
37 39 # config items go here
38 40
39 41 Required configuration items:
40 42
41 43 config = /path/to/file # file containing subscriptions
42 44
43 45 Optional configuration items:
44 46
45 47 test = True # print messages to stdout for testing
46 48 strip = 3 # number of slashes to strip for url paths
47 49 domain = example.com # domain to use if committer missing domain
48 50 style = ... # style file to use when formatting email
49 51 template = ... # template to use when formatting email
50 52 incoming = ... # template to use when run as incoming hook
51 changegroup = ... # template when run as changegroup hook
53 outgoing = ... # template to use when run as outgoing hook
54 changegroup = ... # template to use when run as changegroup hook
52 55 maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
53 56 maxsubject = 67 # truncate subject line longer than this
54 57 diffstat = True # add a diffstat before the diff content
55 58 sources = serve # notify if source of incoming changes in this list
56 59 # (serve == ssh or http, push, pull, bundle)
57 60 merge = False # send notification for merges (default True)
58 61 [email]
59 62 from = user@host.com # email address to send as if none given
60 63 [web]
61 64 baseurl = http://hgserver/... # root of hg web site for browsing commits
62 65
63 66 The notify config file has same format as a regular hgrc file. It has two
64 67 sections so you can express subscriptions in whatever way is handier for you.
65 68
66 69 [usersubs]
67 70 # key is subscriber email, value is ","-separated list of glob patterns
68 71 user@host = pattern
69 72
70 73 [reposubs]
71 74 # key is glob pattern, value is ","-separated list of subscriber emails
72 75 pattern = user@host
73 76
74 77 Glob patterns are matched against path to repository root.
75 78
76 79 If you like, you can put notify config file in repository that users can push
77 80 changes to, they can manage their own subscriptions.
78 81
79 82 no commands defined
80 83 $ hg init a
81 84 $ echo a > a/a
82 85
83 86 commit
84 87
85 88 $ hg --cwd a commit -Ama -d '0 0'
86 89 adding a
87 90
88 91
89 92 clone
90 93
91 94 $ hg --traceback clone a b
92 95 updating to branch default
93 96 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
94 97 $ echo a >> a/a
95 98
96 99 commit
97 100
98 101 $ hg --traceback --cwd a commit -Amb -d '1 0'
99 102
100 103 on Mac OS X 10.5 the tmp path is very long so would get stripped in the subject line
101 104
102 105 $ cat <<EOF >> $HGRCPATH
103 106 > [notify]
104 107 > maxsubject = 200
105 108 > EOF
106 109
107 110 the python call below wraps continuation lines, which appear on Mac OS X 10.5 because
108 111 of the very long subject line
109 112 pull (minimal config)
110 113
111 114 $ hg --traceback --cwd b pull ../a | \
112 115 > python -c 'import sys,re; print re.sub("\n[\t ]", " ", sys.stdin.read()),'
113 116 pulling from ../a
114 117 searching for changes
115 118 adding changesets
116 119 adding manifests
117 120 adding file changes
118 121 added 1 changesets with 1 changes to 1 files
119 122 Content-Type: text/plain; charset="us-ascii"
120 123 MIME-Version: 1.0
121 124 Content-Transfer-Encoding: 7bit
122 125 Date: * (glob)
123 126 Subject: changeset in $TESTTMP/b: b
124 127 From: test
125 128 X-Hg-Notification: changeset 0647d048b600
126 129 Message-Id: <*> (glob)
127 130 To: baz, foo@bar
128 131
129 132 changeset 0647d048b600 in $TESTTMP/b
130 133 details: $TESTTMP/b?cmd=changeset;node=0647d048b600
131 134 description: b
132 135
133 136 diffs (6 lines):
134 137
135 138 diff -r cb9a9f314b8b -r 0647d048b600 a
136 139 --- a/a Thu Jan 01 00:00:00 1970 +0000
137 140 +++ b/a Thu Jan 01 00:00:01 1970 +0000
138 141 @@ -1,1 +1,2 @@ a
139 142 +a
140 143 (run 'hg update' to get a working copy)
141 144 $ cat <<EOF >> $HGRCPATH
142 145 > [notify]
143 146 > config = `pwd`/.notify.conf
144 147 > domain = test.com
145 148 > strip = 42
146 149 > template = Subject: {desc|firstline|strip}\nFrom: {author}\nX-Test: foo\n\nchangeset {node|short} in {webroot}\ndescription:\n\t{desc|tabindent|strip}
147 150 >
148 151 > [web]
149 152 > baseurl = http://test/
150 153 > EOF
151 154
152 155 fail for config file is missing
153 156
154 157 $ hg --cwd b rollback
155 158 repository tip rolled back to revision 0 (undo pull)
156 159 working directory now based on revision 0
157 160 $ hg --cwd b pull ../a 2>&1 | grep 'error.*\.notify\.conf' > /dev/null && echo pull failed
158 161 pull failed
159 162 $ touch ".notify.conf"
160 163
161 164 pull
162 165
163 166 $ hg --cwd b rollback
164 167 repository tip rolled back to revision 0 (undo pull)
165 168 working directory now based on revision 0
166 169 $ hg --traceback --cwd b pull ../a | \
167 170 > python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
168 171 pulling from ../a
169 172 searching for changes
170 173 adding changesets
171 174 adding manifests
172 175 adding file changes
173 176 added 1 changesets with 1 changes to 1 files
174 177 Content-Type: text/plain; charset="us-ascii"
175 178 MIME-Version: 1.0
176 179 Content-Transfer-Encoding: 7bit
177 180 X-Test: foo
178 181 Date: * (glob)
179 182 Subject: b
180 183 From: test@test.com
181 184 X-Hg-Notification: changeset 0647d048b600
182 185 Message-Id: <*> (glob)
183 186 To: baz@test.com, foo@bar
184 187
185 188 changeset 0647d048b600 in b
186 189 description: b
187 190 diffs (6 lines):
188 191
189 192 diff -r cb9a9f314b8b -r 0647d048b600 a
190 193 --- a/a Thu Jan 01 00:00:00 1970 +0000
191 194 +++ b/a Thu Jan 01 00:00:01 1970 +0000
192 195 @@ -1,1 +1,2 @@
193 196 a
194 197 +a
195 198 (run 'hg update' to get a working copy)
196 199
197 200 $ cat << EOF >> $HGRCPATH
198 201 > [hooks]
199 202 > incoming.notify = python:hgext.notify.hook
200 203 >
201 204 > [notify]
202 205 > sources = pull
203 206 > diffstat = True
204 207 > EOF
205 208
206 209 pull
207 210
208 211 $ hg --cwd b rollback
209 212 repository tip rolled back to revision 0 (undo pull)
210 213 working directory now based on revision 0
211 214 $ hg --traceback --cwd b pull ../a | \
212 215 > python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
213 216 pulling from ../a
214 217 searching for changes
215 218 adding changesets
216 219 adding manifests
217 220 adding file changes
218 221 added 1 changesets with 1 changes to 1 files
219 222 Content-Type: text/plain; charset="us-ascii"
220 223 MIME-Version: 1.0
221 224 Content-Transfer-Encoding: 7bit
222 225 X-Test: foo
223 226 Date: * (glob)
224 227 Subject: b
225 228 From: test@test.com
226 229 X-Hg-Notification: changeset 0647d048b600
227 230 Message-Id: <*> (glob)
228 231 To: baz@test.com, foo@bar
229 232
230 233 changeset 0647d048b600 in b
231 234 description: b
232 235 diffstat:
233 236
234 237 a | 1 +
235 238 1 files changed, 1 insertions(+), 0 deletions(-)
236 239
237 240 diffs (6 lines):
238 241
239 242 diff -r cb9a9f314b8b -r 0647d048b600 a
240 243 --- a/a Thu Jan 01 00:00:00 1970 +0000
241 244 +++ b/a Thu Jan 01 00:00:01 1970 +0000
242 245 @@ -1,1 +1,2 @@
243 246 a
244 247 +a
245 248 (run 'hg update' to get a working copy)
246 249
247 250 test merge
248 251
249 252 $ cd a
250 253 $ hg up -C 0
251 254 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
252 255 $ echo a >> a
253 256 $ hg ci -Am adda2 -d '2 0'
254 257 created new head
255 258 $ hg merge
256 259 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
257 260 (branch merge, don't forget to commit)
258 261 $ hg ci -m merge -d '3 0'
259 262 $ cd ..
260 263 $ hg --traceback --cwd b pull ../a | \
261 264 > python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
262 265 pulling from ../a
263 266 searching for changes
264 267 adding changesets
265 268 adding manifests
266 269 adding file changes
267 270 added 2 changesets with 0 changes to 0 files
268 271 Content-Type: text/plain; charset="us-ascii"
269 272 MIME-Version: 1.0
270 273 Content-Transfer-Encoding: 7bit
271 274 X-Test: foo
272 275 Date: * (glob)
273 276 Subject: adda2
274 277 From: test@test.com
275 278 X-Hg-Notification: changeset 0a184ce6067f
276 279 Message-Id: <*> (glob)
277 280 To: baz@test.com, foo@bar
278 281
279 282 changeset 0a184ce6067f in b
280 283 description: adda2
281 284 diffstat:
282 285
283 286 a | 1 +
284 287 1 files changed, 1 insertions(+), 0 deletions(-)
285 288
286 289 diffs (6 lines):
287 290
288 291 diff -r cb9a9f314b8b -r 0a184ce6067f a
289 292 --- a/a Thu Jan 01 00:00:00 1970 +0000
290 293 +++ b/a Thu Jan 01 00:00:02 1970 +0000
291 294 @@ -1,1 +1,2 @@
292 295 a
293 296 +a
294 297 Content-Type: text/plain; charset="us-ascii"
295 298 MIME-Version: 1.0
296 299 Content-Transfer-Encoding: 7bit
297 300 X-Test: foo
298 301 Date: * (glob)
299 302 Subject: merge
300 303 From: test@test.com
301 304 X-Hg-Notification: changeset 6a0cf76b2701
302 305 Message-Id: <*> (glob)
303 306 To: baz@test.com, foo@bar
304 307
305 308 changeset 6a0cf76b2701 in b
306 309 description: merge
307 310 (run 'hg update' to get a working copy)
308 311
309 312 truncate multi-byte subject
310 313
311 314 $ cat <<EOF >> $HGRCPATH
312 315 > [notify]
313 316 > maxsubject = 4
314 317 > EOF
315 318 $ echo a >> a/a
316 319 $ hg --cwd a --encoding utf-8 commit -A -d '0 0' \
317 320 > -m `python -c 'print "\xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4"'`
318 321 $ hg --traceback --cwd b --encoding utf-8 pull ../a | \
319 322 > python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
320 323 pulling from ../a
321 324 searching for changes
322 325 adding changesets
323 326 adding manifests
324 327 adding file changes
325 328 added 1 changesets with 1 changes to 1 files
326 329 Content-Type: text/plain; charset="us-ascii"
327 330 MIME-Version: 1.0
328 331 Content-Transfer-Encoding: 8bit
329 332 X-Test: foo
330 333 Date: * (glob)
331 334 Subject: \xc3\xa0... (esc)
332 335 From: test@test.com
333 336 X-Hg-Notification: changeset 7ea05ad269dc
334 337 Message-Id: <*> (glob)
335 338 To: baz@test.com, foo@bar
336 339
337 340 changeset 7ea05ad269dc in b
338 341 description: \xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4 (esc)
339 342 diffstat:
340 343
341 344 a | 1 +
342 345 1 files changed, 1 insertions(+), 0 deletions(-)
343 346
344 347 diffs (7 lines):
345 348
346 349 diff -r 6a0cf76b2701 -r 7ea05ad269dc a
347 350 --- a/a Thu Jan 01 00:00:03 1970 +0000
348 351 +++ b/a Thu Jan 01 00:00:00 1970 +0000
349 352 @@ -1,2 +1,3 @@
350 353 a
351 354 a
352 355 +a
353 356 (run 'hg update' to get a working copy)
General Comments 0
You need to be logged in to leave comments. Login now