##// END OF EJS Templates
notify: mime-encode messages...
Christian Ebert -
r7116:e981725d default
parent child Browse files
Show More
@@ -1,283 +1,288
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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7 #
7 #
8 # hook extension to email notifications to people when changesets are
8 # hook extension to email notifications to people when changesets are
9 # committed to a repo they subscribe to.
9 # committed to a repo they subscribe to.
10 #
10 #
11 # default mode is to print messages to stdout, for testing and
11 # default mode is to print messages to stdout, for testing and
12 # configuring.
12 # configuring.
13 #
13 #
14 # to use, configure notify extension and enable in hgrc like this:
14 # to use, configure notify extension and enable in hgrc like this:
15 #
15 #
16 # [extensions]
16 # [extensions]
17 # hgext.notify =
17 # hgext.notify =
18 #
18 #
19 # [hooks]
19 # [hooks]
20 # # one email for each incoming changeset
20 # # one email for each incoming changeset
21 # incoming.notify = python:hgext.notify.hook
21 # incoming.notify = python:hgext.notify.hook
22 # # batch emails when many changesets incoming at one time
22 # # batch emails when many changesets incoming at one time
23 # changegroup.notify = python:hgext.notify.hook
23 # changegroup.notify = python:hgext.notify.hook
24 #
24 #
25 # [notify]
25 # [notify]
26 # # config items go in here
26 # # config items go in here
27 #
27 #
28 # config items:
28 # config items:
29 #
29 #
30 # REQUIRED:
30 # REQUIRED:
31 # config = /path/to/file # file containing subscriptions
31 # config = /path/to/file # file containing subscriptions
32 #
32 #
33 # OPTIONAL:
33 # OPTIONAL:
34 # test = True # print messages to stdout for testing
34 # test = True # print messages to stdout for testing
35 # strip = 3 # number of slashes to strip for url paths
35 # strip = 3 # number of slashes to strip for url paths
36 # domain = example.com # domain to use if committer missing domain
36 # domain = example.com # domain to use if committer missing domain
37 # style = ... # style file to use when formatting email
37 # style = ... # style file to use when formatting email
38 # template = ... # template to use when formatting email
38 # template = ... # template to use when formatting email
39 # incoming = ... # template to use when run as incoming hook
39 # incoming = ... # template to use when run as incoming hook
40 # changegroup = ... # template when run as changegroup hook
40 # changegroup = ... # template when run as changegroup hook
41 # maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
41 # maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
42 # maxsubject = 67 # truncate subject line longer than this
42 # maxsubject = 67 # truncate subject line longer than this
43 # diffstat = True # add a diffstat before the diff content
43 # diffstat = True # add a diffstat before the diff content
44 # sources = serve # notify if source of incoming changes in this list
44 # sources = serve # notify if source of incoming changes in this list
45 # # (serve == ssh or http, push, pull, bundle)
45 # # (serve == ssh or http, push, pull, bundle)
46 # [email]
46 # [email]
47 # from = user@host.com # email address to send as if none given
47 # from = user@host.com # email address to send as if none given
48 # [web]
48 # [web]
49 # baseurl = http://hgserver/... # root of hg web site for browsing commits
49 # baseurl = http://hgserver/... # root of hg web site for browsing commits
50 #
50 #
51 # notify config file has same format as regular hgrc. it has two
51 # notify config file has same format as regular hgrc. it has two
52 # sections so you can express subscriptions in whatever way is handier
52 # sections so you can express subscriptions in whatever way is handier
53 # for you.
53 # for you.
54 #
54 #
55 # [usersubs]
55 # [usersubs]
56 # # key is subscriber email, value is ","-separated list of glob patterns
56 # # key is subscriber email, value is ","-separated list of glob patterns
57 # user@host = pattern
57 # user@host = pattern
58 #
58 #
59 # [reposubs]
59 # [reposubs]
60 # # key is glob pattern, value is ","-separated list of subscriber emails
60 # # key is glob pattern, value is ","-separated list of subscriber emails
61 # pattern = user@host
61 # pattern = user@host
62 #
62 #
63 # glob patterns are matched against path to repo root.
63 # glob patterns are matched against path to repo root.
64 #
64 #
65 # if you like, you can put notify config file in repo that users can
65 # if you like, you can put notify config file in repo that users can
66 # push changes to, they can manage their own subscriptions.
66 # push changes to, they can manage their own subscriptions.
67
67
68 from mercurial.i18n import _
68 from mercurial.i18n import _
69 from mercurial.node import bin, short
69 from mercurial.node import bin, short
70 from mercurial import patch, cmdutil, templater, util, mail
70 from mercurial import patch, cmdutil, templater, util, mail
71 import email.Parser, fnmatch, socket, time
71 import email.Parser, fnmatch, socket, time
72
72
73 # template for single changeset can include email headers.
73 # template for single changeset can include email headers.
74 single_template = '''
74 single_template = '''
75 Subject: changeset in {webroot}: {desc|firstline|strip}
75 Subject: changeset in {webroot}: {desc|firstline|strip}
76 From: {author}
76 From: {author}
77
77
78 changeset {node|short} in {root}
78 changeset {node|short} in {root}
79 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
79 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
80 description:
80 description:
81 \t{desc|tabindent|strip}
81 \t{desc|tabindent|strip}
82 '''.lstrip()
82 '''.lstrip()
83
83
84 # template for multiple changesets should not contain email headers,
84 # template for multiple changesets should not contain email headers,
85 # because only first set of headers will be used and result will look
85 # because only first set of headers will be used and result will look
86 # strange.
86 # strange.
87 multiple_template = '''
87 multiple_template = '''
88 changeset {node|short} in {root}
88 changeset {node|short} in {root}
89 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
89 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
90 summary: {desc|firstline}
90 summary: {desc|firstline}
91 '''
91 '''
92
92
93 deftemplates = {
93 deftemplates = {
94 'changegroup': multiple_template,
94 'changegroup': multiple_template,
95 }
95 }
96
96
97 class notifier(object):
97 class notifier(object):
98 '''email notification class.'''
98 '''email notification class.'''
99
99
100 def __init__(self, ui, repo, hooktype):
100 def __init__(self, ui, repo, hooktype):
101 self.ui = ui
101 self.ui = ui
102 cfg = self.ui.config('notify', 'config')
102 cfg = self.ui.config('notify', 'config')
103 if cfg:
103 if cfg:
104 self.ui.readsections(cfg, 'usersubs', 'reposubs')
104 self.ui.readsections(cfg, 'usersubs', 'reposubs')
105 self.repo = repo
105 self.repo = repo
106 self.stripcount = int(self.ui.config('notify', 'strip', 0))
106 self.stripcount = int(self.ui.config('notify', 'strip', 0))
107 self.root = self.strip(self.repo.root)
107 self.root = self.strip(self.repo.root)
108 self.domain = self.ui.config('notify', 'domain')
108 self.domain = self.ui.config('notify', 'domain')
109 self.charsets = mail._charsets(self.ui)
109 self.subs = self.subscribers()
110 self.subs = self.subscribers()
110
111
111 mapfile = self.ui.config('notify', 'style')
112 mapfile = self.ui.config('notify', 'style')
112 template = (self.ui.config('notify', hooktype) or
113 template = (self.ui.config('notify', hooktype) or
113 self.ui.config('notify', 'template'))
114 self.ui.config('notify', 'template'))
114 self.t = cmdutil.changeset_templater(self.ui, self.repo,
115 self.t = cmdutil.changeset_templater(self.ui, self.repo,
115 False, mapfile, False)
116 False, mapfile, False)
116 if not mapfile and not template:
117 if not mapfile and not template:
117 template = deftemplates.get(hooktype) or single_template
118 template = deftemplates.get(hooktype) or single_template
118 if template:
119 if template:
119 template = templater.parsestring(template, quoted=False)
120 template = templater.parsestring(template, quoted=False)
120 self.t.use_template(template)
121 self.t.use_template(template)
121
122
122 def strip(self, path):
123 def strip(self, path):
123 '''strip leading slashes from local path, turn into web-safe path.'''
124 '''strip leading slashes from local path, turn into web-safe path.'''
124
125
125 path = util.pconvert(path)
126 path = util.pconvert(path)
126 count = self.stripcount
127 count = self.stripcount
127 while count > 0:
128 while count > 0:
128 c = path.find('/')
129 c = path.find('/')
129 if c == -1:
130 if c == -1:
130 break
131 break
131 path = path[c+1:]
132 path = path[c+1:]
132 count -= 1
133 count -= 1
133 return path
134 return path
134
135
135 def fixmail(self, addr):
136 def fixmail(self, addr):
136 '''try to clean up email addresses.'''
137 '''try to clean up email addresses.'''
137
138
138 addr = util.email(addr.strip())
139 addr = util.email(addr.strip())
139 if self.domain:
140 if self.domain:
140 a = addr.find('@localhost')
141 a = addr.find('@localhost')
141 if a != -1:
142 if a != -1:
142 addr = addr[:a]
143 addr = addr[:a]
143 if '@' not in addr:
144 if '@' not in addr:
144 return addr + '@' + self.domain
145 return addr + '@' + self.domain
145 return addr
146 return addr
146
147
147 def subscribers(self):
148 def subscribers(self):
148 '''return list of email addresses of subscribers to this repo.'''
149 '''return list of email addresses of subscribers to this repo.'''
149
150
150 subs = {}
151 subs = {}
151 for user, pats in self.ui.configitems('usersubs'):
152 for user, pats in self.ui.configitems('usersubs'):
152 for pat in pats.split(','):
153 for pat in pats.split(','):
153 if fnmatch.fnmatch(self.repo.root, pat.strip()):
154 if fnmatch.fnmatch(self.repo.root, pat.strip()):
154 subs[self.fixmail(user)] = 1
155 subs[self.fixmail(user)] = 1
155 for pat, users in self.ui.configitems('reposubs'):
156 for pat, users in self.ui.configitems('reposubs'):
156 if fnmatch.fnmatch(self.repo.root, pat):
157 if fnmatch.fnmatch(self.repo.root, pat):
157 for user in users.split(','):
158 for user in users.split(','):
158 subs[self.fixmail(user)] = 1
159 subs[self.fixmail(user)] = 1
159 return util.sort(subs)
160 subs = util.sort(subs)
161 return [mail.addressencode(self.ui, s, self.charsets) for s in subs]
160
162
161 def url(self, path=None):
163 def url(self, path=None):
162 return self.ui.config('web', 'baseurl') + (path or self.root)
164 return self.ui.config('web', 'baseurl') + (path or self.root)
163
165
164 def node(self, node):
166 def node(self, node):
165 '''format one changeset.'''
167 '''format one changeset.'''
166
168
167 self.t.show(changenode=node, changes=self.repo.changelog.read(node),
169 self.t.show(changenode=node, changes=self.repo.changelog.read(node),
168 baseurl=self.ui.config('web', 'baseurl'),
170 baseurl=self.ui.config('web', 'baseurl'),
169 root=self.repo.root,
171 root=self.repo.root,
170 webroot=self.root)
172 webroot=self.root)
171
173
172 def skipsource(self, source):
174 def skipsource(self, source):
173 '''true if incoming changes from this source should be skipped.'''
175 '''true if incoming changes from this source should be skipped.'''
174 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
176 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
175 return source not in ok_sources
177 return source not in ok_sources
176
178
177 def send(self, node, count, data):
179 def send(self, node, count, data):
178 '''send message.'''
180 '''send message.'''
179
181
180 p = email.Parser.Parser()
182 p = email.Parser.Parser()
181 msg = p.parsestr(data)
183 msg = p.parsestr(data)
182
184
183 def fix_subject():
185 # store sender and subject
186 sender, subject = msg['From'], msg['Subject']
187 # create fresh mime message from msg body
188 text = msg.get_payload()
189 # for notification prefer readability over data precision
190 msg = mail.mimeencode(self.ui, text, self.charsets)
191
192 def fix_subject(subject):
184 '''try to make subject line exist and be useful.'''
193 '''try to make subject line exist and be useful.'''
185
194
186 subject = msg['Subject']
187 if not subject:
195 if not subject:
188 if count > 1:
196 if count > 1:
189 subject = _('%s: %d new changesets') % (self.root, count)
197 subject = _('%s: %d new changesets') % (self.root, count)
190 else:
198 else:
191 changes = self.repo.changelog.read(node)
199 changes = self.repo.changelog.read(node)
192 s = changes[4].lstrip().split('\n', 1)[0].rstrip()
200 s = changes[4].lstrip().split('\n', 1)[0].rstrip()
193 subject = '%s: %s' % (self.root, s)
201 subject = '%s: %s' % (self.root, s)
194 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
202 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
195 if maxsubject and len(subject) > maxsubject:
203 if maxsubject and len(subject) > maxsubject:
196 subject = subject[:maxsubject-3] + '...'
204 subject = subject[:maxsubject-3] + '...'
197 del msg['Subject']
205 msg['Subject'] = mail.headencode(self.ui, subject, self.charsets)
198 msg['Subject'] = subject
199
206
200 def fix_sender():
207 def fix_sender(sender):
201 '''try to make message have proper sender.'''
208 '''try to make message have proper sender.'''
202
209
203 sender = msg['From']
204 if not sender:
210 if not sender:
205 sender = self.ui.config('email', 'from') or self.ui.username()
211 sender = self.ui.config('email', 'from') or self.ui.username()
206 if '@' not in sender or '@localhost' in sender:
212 if '@' not in sender or '@localhost' in sender:
207 sender = self.fixmail(sender)
213 sender = self.fixmail(sender)
208 del msg['From']
214 msg['From'] = mail.addressencode(self.ui, sender, self.charsets)
209 msg['From'] = sender
210
215
211 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
216 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
212 fix_subject()
217 fix_subject(subject)
213 fix_sender()
218 fix_sender(sender)
214
219
215 msg['X-Hg-Notification'] = 'changeset ' + short(node)
220 msg['X-Hg-Notification'] = 'changeset ' + short(node)
216 if not msg['Message-Id']:
221 if not msg['Message-Id']:
217 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
222 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
218 (short(node), int(time.time()),
223 (short(node), int(time.time()),
219 hash(self.repo.root), socket.getfqdn()))
224 hash(self.repo.root), socket.getfqdn()))
220 msg['To'] = ', '.join(self.subs)
225 msg['To'] = ', '.join(self.subs)
221
226
222 msgtext = msg.as_string(0)
227 msgtext = msg.as_string(0)
223 if self.ui.configbool('notify', 'test', True):
228 if self.ui.configbool('notify', 'test', True):
224 self.ui.write(msgtext)
229 self.ui.write(msgtext)
225 if not msgtext.endswith('\n'):
230 if not msgtext.endswith('\n'):
226 self.ui.write('\n')
231 self.ui.write('\n')
227 else:
232 else:
228 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
233 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
229 (len(self.subs), count))
234 (len(self.subs), count))
230 mail.sendmail(self.ui, util.email(msg['From']),
235 mail.sendmail(self.ui, util.email(msg['From']),
231 self.subs, msgtext)
236 self.subs, msgtext)
232
237
233 def diff(self, node, ref):
238 def diff(self, node, ref):
234 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
239 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
235 prev = self.repo.changelog.parents(node)[0]
240 prev = self.repo.changelog.parents(node)[0]
236
241
237 self.ui.pushbuffer()
242 self.ui.pushbuffer()
238 patch.diff(self.repo, prev, ref, opts=patch.diffopts(self.ui))
243 patch.diff(self.repo, prev, ref, opts=patch.diffopts(self.ui))
239 difflines = self.ui.popbuffer().splitlines()
244 difflines = self.ui.popbuffer().splitlines()
240
245
241 if self.ui.configbool('notify', 'diffstat', True):
246 if self.ui.configbool('notify', 'diffstat', True):
242 s = patch.diffstat(difflines)
247 s = patch.diffstat(difflines)
243 # s may be nil, don't include the header if it is
248 # s may be nil, don't include the header if it is
244 if s:
249 if s:
245 self.ui.write('\ndiffstat:\n\n%s' % s)
250 self.ui.write('\ndiffstat:\n\n%s' % s)
246 if maxdiff == 0:
251 if maxdiff == 0:
247 return
252 return
248 if maxdiff > 0 and len(difflines) > maxdiff:
253 if maxdiff > 0 and len(difflines) > maxdiff:
249 self.ui.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
254 self.ui.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
250 (len(difflines), maxdiff))
255 (len(difflines), maxdiff))
251 difflines = difflines[:maxdiff]
256 difflines = difflines[:maxdiff]
252 elif difflines:
257 elif difflines:
253 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
258 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
254 self.ui.write("\n".join(difflines))
259 self.ui.write("\n".join(difflines))
255
260
256 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
261 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
257 '''send email notifications to interested subscribers.
262 '''send email notifications to interested subscribers.
258
263
259 if used as changegroup hook, send one email for all changesets in
264 if used as changegroup hook, send one email for all changesets in
260 changegroup. else send one email per changeset.'''
265 changegroup. else send one email per changeset.'''
261 n = notifier(ui, repo, hooktype)
266 n = notifier(ui, repo, hooktype)
262 if not n.subs:
267 if not n.subs:
263 ui.debug(_('notify: no subscribers to repo %s\n') % n.root)
268 ui.debug(_('notify: no subscribers to repo %s\n') % n.root)
264 return
269 return
265 if n.skipsource(source):
270 if n.skipsource(source):
266 ui.debug(_('notify: changes have source "%s" - skipping\n') %
271 ui.debug(_('notify: changes have source "%s" - skipping\n') %
267 source)
272 source)
268 return
273 return
269 node = bin(node)
274 node = bin(node)
270 ui.pushbuffer()
275 ui.pushbuffer()
271 if hooktype == 'changegroup':
276 if hooktype == 'changegroup':
272 start = repo[node].rev()
277 start = repo[node].rev()
273 end = len(repo)
278 end = len(repo)
274 count = end - start
279 count = end - start
275 for rev in xrange(start, end):
280 for rev in xrange(start, end):
276 n.node(repo[rev].node())
281 n.node(repo[rev].node())
277 n.diff(node, repo.changelog.tip())
282 n.diff(node, repo.changelog.tip())
278 else:
283 else:
279 count = 1
284 count = 1
280 n.node(node)
285 n.node(node)
281 n.diff(node, node)
286 n.diff(node, node)
282 data = ui.popbuffer()
287 data = ui.popbuffer()
283 n.send(node, count, data)
288 n.send(node, count, data)
@@ -1,343 +1,343
1 #!/bin/sh
1 #!/bin/sh
2
2
3 cat <<EOF >> $HGRCPATH
3 cat <<EOF >> $HGRCPATH
4 [extensions]
4 [extensions]
5 hgext.keyword =
5 hgext.keyword =
6 hgext.mq =
6 hgext.mq =
7 hgext.notify =
7 hgext.notify =
8 [keyword]
8 [keyword]
9 * =
9 * =
10 b = ignore
10 b = ignore
11 [hooks]
11 [hooks]
12 commit=
12 commit=
13 commit.test=cp a hooktest
13 commit.test=cp a hooktest
14 EOF
14 EOF
15
15
16 echo % help
16 echo % help
17 hg help keyword
17 hg help keyword
18
18
19 echo % hg kwdemo
19 echo % hg kwdemo
20 hg --quiet kwdemo --default \
20 hg --quiet kwdemo --default \
21 | sed -e 's![^ ][^ ]*demo.txt,v!/TMP/demo.txt,v!' \
21 | sed -e 's![^ ][^ ]*demo.txt,v!/TMP/demo.txt,v!' \
22 -e 's/,v [a-z0-9][a-z0-9]* /,v xxxxxxxxxxxx /' \
22 -e 's/,v [a-z0-9][a-z0-9]* /,v xxxxxxxxxxxx /' \
23 -e '/[$]Revision/ s/: [a-z0-9][a-z0-9]* /: xxxxxxxxxxxx /' \
23 -e '/[$]Revision/ s/: [a-z0-9][a-z0-9]* /: xxxxxxxxxxxx /' \
24 -e 's! 20[0-9][0-9]/[01][0-9]/[0-3][0-9] [0-2][0-9]:[0-6][0-9]:[0-6][0-9]! 2000/00/00 00:00:00!'
24 -e 's! 20[0-9][0-9]/[01][0-9]/[0-3][0-9] [0-2][0-9]:[0-6][0-9]:[0-6][0-9]! 2000/00/00 00:00:00!'
25
25
26 hg --quiet kwdemo "Branch = {branches}"
26 hg --quiet kwdemo "Branch = {branches}"
27
27
28 hg init Test-bndl
28 hg init Test-bndl
29 cd Test-bndl
29 cd Test-bndl
30
30
31 echo % kwshrink should exit silently in empty/invalid repo
31 echo % kwshrink should exit silently in empty/invalid repo
32 hg kwshrink
32 hg kwshrink
33
33
34 # Symlinks cannot be created on Windows. The bundle was made with:
34 # Symlinks cannot be created on Windows. The bundle was made with:
35 #
35 #
36 # hg init t
36 # hg init t
37 # cd t
37 # cd t
38 # echo a > a
38 # echo a > a
39 # ln -s a sym
39 # ln -s a sym
40 # hg add sym
40 # hg add sym
41 # hg ci -m addsym -u mercurial
41 # hg ci -m addsym -u mercurial
42 # hg bundle --base null ../test-keyword.hg
42 # hg bundle --base null ../test-keyword.hg
43 #
43 #
44 hg pull -u "$TESTDIR/test-keyword.hg" \
44 hg pull -u "$TESTDIR/test-keyword.hg" \
45 | sed 's/pulling from.*test-keyword.hg/pulling from test-keyword.hg/'
45 | sed 's/pulling from.*test-keyword.hg/pulling from test-keyword.hg/'
46
46
47 echo 'expand $Id$' > a
47 echo 'expand $Id$' > a
48 echo 'do not process $Id:' >> a
48 echo 'do not process $Id:' >> a
49 echo 'xxx $' >> a
49 echo 'xxx $' >> a
50 echo 'ignore $Id$' > b
50 echo 'ignore $Id$' > b
51 echo % cat
51 echo % cat
52 cat a b
52 cat a b
53
53
54 echo % addremove
54 echo % addremove
55 hg addremove
55 hg addremove
56 echo % status
56 echo % status
57 hg status
57 hg status
58
58
59 echo % default keyword expansion including commit hook
59 echo % default keyword expansion including commit hook
60 echo % interrupted commit should not change state or run commit hook
60 echo % interrupted commit should not change state or run commit hook
61 hg --debug commit
61 hg --debug commit
62 echo % status
62 echo % status
63 hg status
63 hg status
64
64
65 echo % commit
65 echo % commit
66 hg --debug commit -mabsym -d '0 0' -u 'User Name <user@example.com>'
66 hg --debug commit -mabsym -d '0 0' -u 'User Name <user@example.com>'
67 echo % status
67 echo % status
68 hg status
68 hg status
69 echo % identify
69 echo % identify
70 hg debugrebuildstate
70 hg debugrebuildstate
71 hg --quiet identify
71 hg --quiet identify
72 echo % cat
72 echo % cat
73 cat a b
73 cat a b
74 echo % hg cat
74 echo % hg cat
75 hg cat sym a b
75 hg cat sym a b
76
76
77 echo
77 echo
78 echo % diff a hooktest
78 echo % diff a hooktest
79 diff a hooktest
79 diff a hooktest
80
80
81 echo % removing commit hook from config
81 echo % removing commit hook from config
82 sed -e '/\[hooks\]/,$ d' $HGRCPATH > $HGRCPATH.nohook
82 sed -e '/\[hooks\]/,$ d' $HGRCPATH > $HGRCPATH.nohook
83 mv $HGRCPATH.nohook $HGRCPATH
83 mv $HGRCPATH.nohook $HGRCPATH
84 rm hooktest
84 rm hooktest
85
85
86 echo % bundle
86 echo % bundle
87 hg bundle --base null ../kw.hg
87 hg bundle --base null ../kw.hg
88
88
89 cd ..
89 cd ..
90 hg init Test
90 hg init Test
91 cd Test
91 cd Test
92
92
93 echo % notify on pull to check whether keywords stay as is in email
93 echo % notify on pull to check whether keywords stay as is in email
94 echo % ie. if patch.diff wrapper acts as it should
94 echo % ie. if patch.diff wrapper acts as it should
95
95
96 cat <<EOF >> $HGRCPATH
96 cat <<EOF >> $HGRCPATH
97 [hooks]
97 [hooks]
98 incoming.notify = python:hgext.notify.hook
98 incoming.notify = python:hgext.notify.hook
99 [notify]
99 [notify]
100 sources = pull
100 sources = pull
101 diffstat = False
101 diffstat = False
102 [reposubs]
102 [reposubs]
103 * = Test
103 * = Test
104 EOF
104 EOF
105
105
106 echo % pull from bundle
106 echo % pull from bundle
107 hg pull -u ../kw.hg 2>&1 | sed -e '/^Date:/,/^diffs (/ d'
107 hg pull -u ../kw.hg 2>&1 | sed -e '/^Content-Type:/,/^diffs (/ d'
108
108
109 echo % remove notify config
109 echo % remove notify config
110 sed -e '/\[hooks\]/,$ d' $HGRCPATH > $HGRCPATH.nonotify
110 sed -e '/\[hooks\]/,$ d' $HGRCPATH > $HGRCPATH.nonotify
111 mv $HGRCPATH.nonotify $HGRCPATH
111 mv $HGRCPATH.nonotify $HGRCPATH
112
112
113 echo % touch
113 echo % touch
114 touch a b
114 touch a b
115 echo % status
115 echo % status
116 hg status
116 hg status
117
117
118 rm sym a b
118 rm sym a b
119 echo % update
119 echo % update
120 hg update
120 hg update
121 echo % cat
121 echo % cat
122 cat a b
122 cat a b
123
123
124 echo % check whether expansion is filewise
124 echo % check whether expansion is filewise
125 echo '$Id$' > c
125 echo '$Id$' > c
126 echo 'tests for different changenodes' >> c
126 echo 'tests for different changenodes' >> c
127 echo % commit c
127 echo % commit c
128 hg commit -A -mcndiff -d '1 0' -u 'User Name <user@example.com>'
128 hg commit -A -mcndiff -d '1 0' -u 'User Name <user@example.com>'
129 echo % force expansion
129 echo % force expansion
130 hg -v kwexpand
130 hg -v kwexpand
131 echo % compare changenodes in a c
131 echo % compare changenodes in a c
132 cat a c
132 cat a c
133
133
134 echo % qinit -c
134 echo % qinit -c
135 hg qinit -c
135 hg qinit -c
136 echo % qimport
136 echo % qimport
137 hg qimport -r tip -n mqtest.diff
137 hg qimport -r tip -n mqtest.diff
138 echo % qcommit
138 echo % qcommit
139 hg qcommit -mqtest
139 hg qcommit -mqtest
140 echo % keywords should not be expanded in patch
140 echo % keywords should not be expanded in patch
141 cat .hg/patches/mqtest.diff
141 cat .hg/patches/mqtest.diff
142 echo % qpop
142 echo % qpop
143 hg qpop
143 hg qpop
144 echo % qgoto - should imply qpush
144 echo % qgoto - should imply qpush
145 hg qgoto mqtest.diff
145 hg qgoto mqtest.diff
146 echo % cat
146 echo % cat
147 cat c
147 cat c
148 echo % qpop and move on
148 echo % qpop and move on
149 hg qpop
149 hg qpop
150
150
151 echo % copy
151 echo % copy
152 hg cp a c
152 hg cp a c
153
153
154 echo % kwfiles added
154 echo % kwfiles added
155 hg kwfiles
155 hg kwfiles
156
156
157 echo % commit
157 echo % commit
158 hg --debug commit -ma2c -d '1 0' -u 'User Name <user@example.com>'
158 hg --debug commit -ma2c -d '1 0' -u 'User Name <user@example.com>'
159 echo % cat a c
159 echo % cat a c
160 cat a c
160 cat a c
161 echo % touch copied c
161 echo % touch copied c
162 touch c
162 touch c
163 echo % status
163 echo % status
164 hg status
164 hg status
165
165
166 echo % kwfiles
166 echo % kwfiles
167 hg kwfiles
167 hg kwfiles
168
168
169 echo % diff --rev
169 echo % diff --rev
170 hg diff --rev 1 | grep -v 'b/c'
170 hg diff --rev 1 | grep -v 'b/c'
171
171
172 echo % rollback
172 echo % rollback
173 hg rollback
173 hg rollback
174 echo % status
174 echo % status
175 hg status
175 hg status
176 echo % update -C
176 echo % update -C
177 hg update --clean
177 hg update --clean
178
178
179 echo % custom keyword expansion
179 echo % custom keyword expansion
180 echo % try with kwdemo
180 echo % try with kwdemo
181 hg --quiet kwdemo "Xinfo = {author}: {desc}"
181 hg --quiet kwdemo "Xinfo = {author}: {desc}"
182
182
183 cat <<EOF >>$HGRCPATH
183 cat <<EOF >>$HGRCPATH
184 [keywordmaps]
184 [keywordmaps]
185 Id = {file} {node|short} {date|rfc822date} {author|user}
185 Id = {file} {node|short} {date|rfc822date} {author|user}
186 Xinfo = {author}: {desc}
186 Xinfo = {author}: {desc}
187 EOF
187 EOF
188
188
189 echo % cat
189 echo % cat
190 cat a b
190 cat a b
191 echo % hg cat
191 echo % hg cat
192 hg cat sym a b
192 hg cat sym a b
193
193
194 echo
194 echo
195 echo '$Xinfo$' >> a
195 echo '$Xinfo$' >> a
196 cat <<EOF >> log
196 cat <<EOF >> log
197 firstline
197 firstline
198 secondline
198 secondline
199 EOF
199 EOF
200
200
201 echo % interrupted commit should not change state
201 echo % interrupted commit should not change state
202 hg commit
202 hg commit
203 echo % status
203 echo % status
204 hg status
204 hg status
205
205
206 echo % commit
206 echo % commit
207 hg --debug commit -l log -d '2 0' -u 'User Name <user@example.com>'
207 hg --debug commit -l log -d '2 0' -u 'User Name <user@example.com>'
208 rm log
208 rm log
209 echo % status
209 echo % status
210 hg status
210 hg status
211 echo % verify
211 echo % verify
212 hg verify
212 hg verify
213
213
214 echo % cat
214 echo % cat
215 cat a b
215 cat a b
216 echo % hg cat
216 echo % hg cat
217 hg cat sym a b
217 hg cat sym a b
218 echo
218 echo
219 echo % annotate
219 echo % annotate
220 hg annotate a
220 hg annotate a
221
221
222 echo % remove
222 echo % remove
223 hg debugrebuildstate
223 hg debugrebuildstate
224 hg remove a
224 hg remove a
225 hg --debug commit -m rma
225 hg --debug commit -m rma
226 echo % status
226 echo % status
227 hg status
227 hg status
228 echo % rollback
228 echo % rollback
229 hg rollback
229 hg rollback
230 echo % status
230 echo % status
231 hg status
231 hg status
232 echo % revert a
232 echo % revert a
233 hg revert --no-backup --rev tip a
233 hg revert --no-backup --rev tip a
234 echo % cat a
234 echo % cat a
235 cat a
235 cat a
236
236
237 echo % clone to test incoming
237 echo % clone to test incoming
238 cd ..
238 cd ..
239 hg clone -r1 Test Test-a
239 hg clone -r1 Test Test-a
240 cd Test-a
240 cd Test-a
241 cat <<EOF >> .hg/hgrc
241 cat <<EOF >> .hg/hgrc
242 [paths]
242 [paths]
243 default = ../Test
243 default = ../Test
244 EOF
244 EOF
245 echo % incoming
245 echo % incoming
246 # remove path to temp dir
246 # remove path to temp dir
247 hg incoming | sed -e 's/^\(comparing with \).*\(test-keyword.*\)/\1\2/'
247 hg incoming | sed -e 's/^\(comparing with \).*\(test-keyword.*\)/\1\2/'
248
248
249 sed -e 's/Id.*/& rejecttest/' a > a.new
249 sed -e 's/Id.*/& rejecttest/' a > a.new
250 mv a.new a
250 mv a.new a
251 echo % commit rejecttest
251 echo % commit rejecttest
252 hg --debug commit -m'rejects?' -d '3 0' -u 'User Name <user@example.com>'
252 hg --debug commit -m'rejects?' -d '3 0' -u 'User Name <user@example.com>'
253 echo % export
253 echo % export
254 hg export -o ../rejecttest.diff tip
254 hg export -o ../rejecttest.diff tip
255
255
256 cd ../Test
256 cd ../Test
257 echo % import
257 echo % import
258 hg import ../rejecttest.diff
258 hg import ../rejecttest.diff
259 echo % cat
259 echo % cat
260 cat a b
260 cat a b
261 echo
261 echo
262 echo % rollback
262 echo % rollback
263 hg rollback
263 hg rollback
264 echo % clean update
264 echo % clean update
265 hg update --clean
265 hg update --clean
266
266
267 echo % kwexpand/kwshrink on selected files
267 echo % kwexpand/kwshrink on selected files
268 mkdir x
268 mkdir x
269 echo % copy a x/a
269 echo % copy a x/a
270 hg copy a x/a
270 hg copy a x/a
271 echo % kwexpand a
271 echo % kwexpand a
272 hg --verbose kwexpand a
272 hg --verbose kwexpand a
273 echo % kwexpand x/a should abort
273 echo % kwexpand x/a should abort
274 hg --verbose kwexpand x/a
274 hg --verbose kwexpand x/a
275 cd x
275 cd x
276 hg --debug commit -m xa -d '3 0' -u 'User Name <user@example.com>'
276 hg --debug commit -m xa -d '3 0' -u 'User Name <user@example.com>'
277 echo % cat a
277 echo % cat a
278 cat a
278 cat a
279 echo % kwshrink a inside directory x
279 echo % kwshrink a inside directory x
280 hg --verbose kwshrink a
280 hg --verbose kwshrink a
281 echo % cat a
281 echo % cat a
282 cat a
282 cat a
283 cd ..
283 cd ..
284
284
285 echo % kwexpand nonexistent
285 echo % kwexpand nonexistent
286 hg kwexpand nonexistent 2>&1 | sed 's/nonexistent:.*/nonexistent:/'
286 hg kwexpand nonexistent 2>&1 | sed 's/nonexistent:.*/nonexistent:/'
287
287
288 echo % hg serve
288 echo % hg serve
289 hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
289 hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
290 cat hg.pid >> $DAEMON_PIDS
290 cat hg.pid >> $DAEMON_PIDS
291 echo % expansion
291 echo % expansion
292 echo % hgweb file
292 echo % hgweb file
293 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/a/?style=raw')
293 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/a/?style=raw')
294 echo % no expansion
294 echo % no expansion
295 echo % hgweb annotate
295 echo % hgweb annotate
296 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/annotate/tip/a/?style=raw')
296 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/annotate/tip/a/?style=raw')
297 echo % hgweb changeset
297 echo % hgweb changeset
298 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/rev/tip/?style=raw')
298 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/rev/tip/?style=raw')
299 echo % hgweb filediff
299 echo % hgweb filediff
300 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/diff/bb948857c743/a?style=raw')
300 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/diff/bb948857c743/a?style=raw')
301 echo % errors encountered
301 echo % errors encountered
302 cat errors.log
302 cat errors.log
303
303
304 echo % merge/resolve
304 echo % merge/resolve
305 echo '$Id$' > m
305 echo '$Id$' > m
306 hg add m
306 hg add m
307 hg commit -m 4kw
307 hg commit -m 4kw
308 echo foo >> m
308 echo foo >> m
309 hg commit -m 5foo
309 hg commit -m 5foo
310 echo % simplemerge
310 echo % simplemerge
311 hg update 4
311 hg update 4
312 echo foo >> m
312 echo foo >> m
313 hg commit -m 6foo
313 hg commit -m 6foo
314 hg merge
314 hg merge
315 hg commit -m simplemerge
315 hg commit -m simplemerge
316 cat m
316 cat m
317 echo % conflict
317 echo % conflict
318 hg update 4
318 hg update 4
319 echo bar >> m
319 echo bar >> m
320 hg commit -m 8bar
320 hg commit -m 8bar
321 hg merge
321 hg merge
322 echo % keyword stays outside conflict zone
322 echo % keyword stays outside conflict zone
323 cat m
323 cat m
324 echo % resolve to local
324 echo % resolve to local
325 HGMERGE=internal:local hg resolve
325 HGMERGE=internal:local hg resolve
326 hg commit -m localresolve
326 hg commit -m localresolve
327 cat m
327 cat m
328
328
329 echo % switch off expansion
329 echo % switch off expansion
330 echo % kwshrink with unknown file u
330 echo % kwshrink with unknown file u
331 cp a u
331 cp a u
332 hg --verbose kwshrink
332 hg --verbose kwshrink
333 echo % cat
333 echo % cat
334 cat a b
334 cat a b
335 echo % hg cat
335 echo % hg cat
336 hg cat sym a b
336 hg cat sym a b
337 echo
337 echo
338 rm $HGRCPATH
338 rm $HGRCPATH
339 echo % cat
339 echo % cat
340 cat a b
340 cat a b
341 echo % hg cat
341 echo % hg cat
342 hg cat sym a b
342 hg cat sym a b
343 echo
343 echo
@@ -1,36 +1,39
1 % clone
1 % clone
2 updating working directory
2 updating working directory
3 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
3 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
4 % commit
4 % commit
5 adding a
5 adding a
6 % commit
6 % commit
7 % push
7 % push
8 pushing to ../a
8 pushing to ../a
9 searching for changes
9 searching for changes
10 adding changesets
10 adding changesets
11 adding manifests
11 adding manifests
12 adding file changes
12 adding file changes
13 added 2 changesets with 2 changes to 1 files
13 added 2 changesets with 2 changes to 1 files
14 Content-Type: text/plain; charset="us-ascii"
15 MIME-Version: 1.0
16 Content-Transfer-Encoding: 7bit
14 Date:
17 Date:
15 Subject: test-notify-changegroup/a: 2 new changesets
18 Subject: test-notify-changegroup/a: 2 new changesets
16 From: test
19 From: test
17 X-Hg-Notification: changeset cb9a9f314b8b
20 X-Hg-Notification: changeset cb9a9f314b8b
18 Message-Id:
21 Message-Id:
19 To: baz, foo@bar
22 To: baz, foo@bar
20
23
21 changeset cb9a9f314b8b in test-notify-changegroup/a
24 changeset cb9a9f314b8b in test-notify-changegroup/a
22 details: test-notify-changegroup/a?cmd=changeset;node=cb9a9f314b8b
25 details: test-notify-changegroup/a?cmd=changeset;node=cb9a9f314b8b
23 summary: a
26 summary: a
24
27
25 changeset ba677d0156c1 in test-notify-changegroup/a
28 changeset ba677d0156c1 in test-notify-changegroup/a
26 details: test-notify-changegroup/a?cmd=changeset;node=ba677d0156c1
29 details: test-notify-changegroup/a?cmd=changeset;node=ba677d0156c1
27 summary: b
30 summary: b
28
31
29 diffs (6 lines):
32 diffs (6 lines):
30
33
31 diff -r 000000000000 -r ba677d0156c1 a
34 diff -r 000000000000 -r ba677d0156c1 a
32 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
35 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
33 +++ b/a Thu Jan 01 00:00:00 1970 +0000
36 +++ b/a Thu Jan 01 00:00:00 1970 +0000
34 @@ -0,0 +1,2 @@
37 @@ -0,0 +1,2 @@
35 +a
38 +a
36 +a
39 +a
@@ -1,98 +1,107
1 notify extension - No help text available
1 notify extension - No help text available
2
2
3 no commands defined
3 no commands defined
4 % commit
4 % commit
5 adding a
5 adding a
6 % clone
6 % clone
7 updating working directory
7 updating working directory
8 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
8 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
9 % commit
9 % commit
10 % pull (minimal config)
10 % pull (minimal config)
11 pulling from ../a
11 pulling from ../a
12 searching for changes
12 searching for changes
13 adding changesets
13 adding changesets
14 adding manifests
14 adding manifests
15 adding file changes
15 adding file changes
16 added 1 changesets with 1 changes to 1 files
16 added 1 changesets with 1 changes to 1 files
17 Content-Type: text/plain; charset="us-ascii"
18 MIME-Version: 1.0
19 Content-Transfer-Encoding: 7bit
17 Date:
20 Date:
18 Subject: changeset in test-notify/b: b
21 Subject: changeset in test-notify/b: b
19 From: test
22 From: test
20 X-Hg-Notification: changeset 0647d048b600
23 X-Hg-Notification: changeset 0647d048b600
21 Message-Id:
24 Message-Id:
22 To: baz, foo@bar
25 To: baz, foo@bar
23
26
24 changeset 0647d048b600 in test-notify/b
27 changeset 0647d048b600 in test-notify/b
25 details: test-notify/b?cmd=changeset;node=0647d048b600
28 details: test-notify/b?cmd=changeset;node=0647d048b600
26 description: b
29 description: b
27
30
28 diffs (6 lines):
31 diffs (6 lines):
29
32
30 diff -r cb9a9f314b8b -r 0647d048b600 a
33 diff -r cb9a9f314b8b -r 0647d048b600 a
31 --- a/a Thu Jan 01 00:00:00 1970 +0000
34 --- a/a Thu Jan 01 00:00:00 1970 +0000
32 +++ b/a Thu Jan 01 00:00:01 1970 +0000
35 +++ b/a Thu Jan 01 00:00:01 1970 +0000
33 @@ -1,1 +1,2 @@
36 @@ -1,1 +1,2 @@
34 a
37 a
35 +a
38 +a
36 (run 'hg update' to get a working copy)
39 (run 'hg update' to get a working copy)
37 % fail for config file is missing
40 % fail for config file is missing
38 rolling back last transaction
41 rolling back last transaction
39 pull failed
42 pull failed
40 % pull
43 % pull
41 rolling back last transaction
44 rolling back last transaction
42 pulling from ../a
45 pulling from ../a
43 searching for changes
46 searching for changes
44 adding changesets
47 adding changesets
45 adding manifests
48 adding manifests
46 adding file changes
49 adding file changes
47 added 1 changesets with 1 changes to 1 files
50 added 1 changesets with 1 changes to 1 files
51 Content-Type: text/plain; charset="us-ascii"
52 MIME-Version: 1.0
53 Content-Transfer-Encoding: 7bit
48 Date:
54 Date:
49 Subject: b
55 Subject: b
50 From: test@test.com
56 From: test@test.com
51 X-Hg-Notification: changeset 0647d048b600
57 X-Hg-Notification: changeset 0647d048b600
52 Message-Id:
58 Message-Id:
53 To: baz@test.com, foo@bar
59 To: baz@test.com, foo@bar
54
60
55 changeset 0647d048b600
61 changeset 0647d048b600
56 description:
62 description:
57 b
63 b
58 diffs (6 lines):
64 diffs (6 lines):
59
65
60 diff -r cb9a9f314b8b -r 0647d048b600 a
66 diff -r cb9a9f314b8b -r 0647d048b600 a
61 --- a/a Thu Jan 01 00:00:00 1970 +0000
67 --- a/a Thu Jan 01 00:00:00 1970 +0000
62 +++ b/a Thu Jan 01 00:00:01 1970 +0000
68 +++ b/a Thu Jan 01 00:00:01 1970 +0000
63 @@ -1,1 +1,2 @@
69 @@ -1,1 +1,2 @@
64 a
70 a
65 +a
71 +a
66 (run 'hg update' to get a working copy)
72 (run 'hg update' to get a working copy)
67 % pull
73 % pull
68 rolling back last transaction
74 rolling back last transaction
69 pulling from ../a
75 pulling from ../a
70 searching for changes
76 searching for changes
71 adding changesets
77 adding changesets
72 adding manifests
78 adding manifests
73 adding file changes
79 adding file changes
74 added 1 changesets with 1 changes to 1 files
80 added 1 changesets with 1 changes to 1 files
81 Content-Type: text/plain; charset="us-ascii"
82 MIME-Version: 1.0
83 Content-Transfer-Encoding: 7bit
75 Date:
84 Date:
76 Subject: b
85 Subject: b
77 From: test@test.com
86 From: test@test.com
78 X-Hg-Notification: changeset 0647d048b600
87 X-Hg-Notification: changeset 0647d048b600
79 Message-Id:
88 Message-Id:
80 To: baz@test.com, foo@bar
89 To: baz@test.com, foo@bar
81
90
82 changeset 0647d048b600
91 changeset 0647d048b600
83 description:
92 description:
84 b
93 b
85 diffstat:
94 diffstat:
86
95
87 1 file changed, 1 insertion(+)
96 1 file changed, 1 insertion(+)
88 a | 1 +
97 a | 1 +
89
98
90 diffs (6 lines):
99 diffs (6 lines):
91
100
92 diff -r cb9a9f314b8b -r 0647d048b600 a
101 diff -r cb9a9f314b8b -r 0647d048b600 a
93 --- a/a Thu Jan 01 00:00:00 1970 +0000
102 --- a/a Thu Jan 01 00:00:00 1970 +0000
94 +++ b/a Thu Jan 01 00:00:01 1970 +0000
103 +++ b/a Thu Jan 01 00:00:01 1970 +0000
95 @@ -1,1 +1,2 @@
104 @@ -1,1 +1,2 @@
96 a
105 a
97 +a
106 +a
98 (run 'hg update' to get a working copy)
107 (run 'hg update' to get a working copy)
General Comments 0
You need to be logged in to leave comments. Login now