##// END OF EJS Templates
Add sending date to notify message....
Mathieu Clabaut -
r4479:afa1f57a default
parent child Browse files
Show More
@@ -1,283 +1,285 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
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.demandload import *
68 from mercurial.demandload import *
69 from mercurial.i18n import gettext as _
69 from mercurial.i18n import gettext as _
70 from mercurial.node import *
70 from mercurial.node import *
71 demandload(globals(), 'mercurial:patch,cmdutil,templater,util,mail')
71 demandload(globals(), 'mercurial:patch,cmdutil,templater,util,mail')
72 demandload(globals(), 'email.Parser fnmatch socket time')
72 demandload(globals(), 'email.Parser fnmatch socket time')
73
73
74 # template for single changeset can include email headers.
74 # template for single changeset can include email headers.
75 single_template = '''
75 single_template = '''
76 Subject: changeset in {webroot}: {desc|firstline|strip}
76 Subject: changeset in {webroot}: {desc|firstline|strip}
77 From: {author}
77 From: {author}
78
78
79 changeset {node|short} in {root}
79 changeset {node|short} in {root}
80 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
80 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
81 description:
81 description:
82 \t{desc|tabindent|strip}
82 \t{desc|tabindent|strip}
83 '''.lstrip()
83 '''.lstrip()
84
84
85 # template for multiple changesets should not contain email headers,
85 # template for multiple changesets should not contain email headers,
86 # because only first set of headers will be used and result will look
86 # because only first set of headers will be used and result will look
87 # strange.
87 # strange.
88 multiple_template = '''
88 multiple_template = '''
89 changeset {node|short} in {root}
89 changeset {node|short} in {root}
90 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
90 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
91 summary: {desc|firstline}
91 summary: {desc|firstline}
92 '''
92 '''
93
93
94 deftemplates = {
94 deftemplates = {
95 'changegroup': multiple_template,
95 'changegroup': multiple_template,
96 }
96 }
97
97
98 class notifier(object):
98 class notifier(object):
99 '''email notification class.'''
99 '''email notification class.'''
100
100
101 def __init__(self, ui, repo, hooktype):
101 def __init__(self, ui, repo, hooktype):
102 self.ui = ui
102 self.ui = ui
103 cfg = self.ui.config('notify', 'config')
103 cfg = self.ui.config('notify', 'config')
104 if cfg:
104 if cfg:
105 self.ui.readsections(cfg, 'usersubs', 'reposubs')
105 self.ui.readsections(cfg, 'usersubs', 'reposubs')
106 self.repo = repo
106 self.repo = repo
107 self.stripcount = int(self.ui.config('notify', 'strip', 0))
107 self.stripcount = int(self.ui.config('notify', 'strip', 0))
108 self.root = self.strip(self.repo.root)
108 self.root = self.strip(self.repo.root)
109 self.domain = self.ui.config('notify', 'domain')
109 self.domain = self.ui.config('notify', 'domain')
110 self.subs = self.subscribers()
110 self.subs = self.subscribers()
111
111
112 mapfile = self.ui.config('notify', 'style')
112 mapfile = self.ui.config('notify', 'style')
113 template = (self.ui.config('notify', hooktype) or
113 template = (self.ui.config('notify', hooktype) or
114 self.ui.config('notify', 'template'))
114 self.ui.config('notify', 'template'))
115 self.t = cmdutil.changeset_templater(self.ui, self.repo,
115 self.t = cmdutil.changeset_templater(self.ui, self.repo,
116 False, None, mapfile, False)
116 False, None, mapfile, False)
117 if not mapfile and not template:
117 if not mapfile and not template:
118 template = deftemplates.get(hooktype) or single_template
118 template = deftemplates.get(hooktype) or single_template
119 if template:
119 if template:
120 template = templater.parsestring(template, quoted=False)
120 template = templater.parsestring(template, quoted=False)
121 self.t.use_template(template)
121 self.t.use_template(template)
122
122
123 def strip(self, path):
123 def strip(self, path):
124 '''strip leading slashes from local path, turn into web-safe path.'''
124 '''strip leading slashes from local path, turn into web-safe path.'''
125
125
126 path = util.pconvert(path)
126 path = util.pconvert(path)
127 count = self.stripcount
127 count = self.stripcount
128 while count > 0:
128 while count > 0:
129 c = path.find('/')
129 c = path.find('/')
130 if c == -1:
130 if c == -1:
131 break
131 break
132 path = path[c+1:]
132 path = path[c+1:]
133 count -= 1
133 count -= 1
134 return path
134 return path
135
135
136 def fixmail(self, addr):
136 def fixmail(self, addr):
137 '''try to clean up email addresses.'''
137 '''try to clean up email addresses.'''
138
138
139 addr = templater.email(addr.strip())
139 addr = templater.email(addr.strip())
140 if self.domain:
140 if self.domain:
141 a = addr.find('@localhost')
141 a = addr.find('@localhost')
142 if a != -1:
142 if a != -1:
143 addr = addr[:a]
143 addr = addr[:a]
144 if '@' not in addr:
144 if '@' not in addr:
145 return addr + '@' + self.domain
145 return addr + '@' + self.domain
146 return addr
146 return addr
147
147
148 def subscribers(self):
148 def subscribers(self):
149 '''return list of email addresses of subscribers to this repo.'''
149 '''return list of email addresses of subscribers to this repo.'''
150
150
151 subs = {}
151 subs = {}
152 for user, pats in self.ui.configitems('usersubs'):
152 for user, pats in self.ui.configitems('usersubs'):
153 for pat in pats.split(','):
153 for pat in pats.split(','):
154 if fnmatch.fnmatch(self.repo.root, pat.strip()):
154 if fnmatch.fnmatch(self.repo.root, pat.strip()):
155 subs[self.fixmail(user)] = 1
155 subs[self.fixmail(user)] = 1
156 for pat, users in self.ui.configitems('reposubs'):
156 for pat, users in self.ui.configitems('reposubs'):
157 if fnmatch.fnmatch(self.repo.root, pat):
157 if fnmatch.fnmatch(self.repo.root, pat):
158 for user in users.split(','):
158 for user in users.split(','):
159 subs[self.fixmail(user)] = 1
159 subs[self.fixmail(user)] = 1
160 subs = subs.keys()
160 subs = subs.keys()
161 subs.sort()
161 subs.sort()
162 return subs
162 return subs
163
163
164 def url(self, path=None):
164 def url(self, path=None):
165 return self.ui.config('web', 'baseurl') + (path or self.root)
165 return self.ui.config('web', 'baseurl') + (path or self.root)
166
166
167 def node(self, node):
167 def node(self, node):
168 '''format one changeset.'''
168 '''format one changeset.'''
169
169
170 self.t.show(changenode=node, changes=self.repo.changelog.read(node),
170 self.t.show(changenode=node, changes=self.repo.changelog.read(node),
171 baseurl=self.ui.config('web', 'baseurl'),
171 baseurl=self.ui.config('web', 'baseurl'),
172 root=self.repo.root,
172 root=self.repo.root,
173 webroot=self.root)
173 webroot=self.root)
174
174
175 def skipsource(self, source):
175 def skipsource(self, source):
176 '''true if incoming changes from this source should be skipped.'''
176 '''true if incoming changes from this source should be skipped.'''
177 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
177 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
178 return source not in ok_sources
178 return source not in ok_sources
179
179
180 def send(self, node, count, data):
180 def send(self, node, count, data):
181 '''send message.'''
181 '''send message.'''
182
182
183 p = email.Parser.Parser()
183 p = email.Parser.Parser()
184 msg = p.parsestr(data)
184 msg = p.parsestr(data)
185
185
186 def fix_subject():
186 def fix_subject():
187 '''try to make subject line exist and be useful.'''
187 '''try to make subject line exist and be useful.'''
188
188
189 subject = msg['Subject']
189 subject = msg['Subject']
190 if not subject:
190 if not subject:
191 if count > 1:
191 if count > 1:
192 subject = _('%s: %d new changesets') % (self.root, count)
192 subject = _('%s: %d new changesets') % (self.root, count)
193 else:
193 else:
194 changes = self.repo.changelog.read(node)
194 changes = self.repo.changelog.read(node)
195 s = changes[4].lstrip().split('\n', 1)[0].rstrip()
195 s = changes[4].lstrip().split('\n', 1)[0].rstrip()
196 subject = '%s: %s' % (self.root, s)
196 subject = '%s: %s' % (self.root, s)
197 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
197 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
198 if maxsubject and len(subject) > maxsubject:
198 if maxsubject and len(subject) > maxsubject:
199 subject = subject[:maxsubject-3] + '...'
199 subject = subject[:maxsubject-3] + '...'
200 del msg['Subject']
200 del msg['Subject']
201 msg['Subject'] = subject
201 msg['Subject'] = subject
202
202
203 def fix_sender():
203 def fix_sender():
204 '''try to make message have proper sender.'''
204 '''try to make message have proper sender.'''
205
205
206 sender = msg['From']
206 sender = msg['From']
207 if not sender:
207 if not sender:
208 sender = self.ui.config('email', 'from') or self.ui.username()
208 sender = self.ui.config('email', 'from') or self.ui.username()
209 if '@' not in sender or '@localhost' in sender:
209 if '@' not in sender or '@localhost' in sender:
210 sender = self.fixmail(sender)
210 sender = self.fixmail(sender)
211 del msg['From']
211 del msg['From']
212 msg['From'] = sender
212 msg['From'] = sender
213
213
214 msg['Date'] = util.datestr(date=util.makedate(),
215 format="%a, %d %b %Y %H:%M:%S", timezone=True)
214 fix_subject()
216 fix_subject()
215 fix_sender()
217 fix_sender()
216
218
217 msg['X-Hg-Notification'] = 'changeset ' + short(node)
219 msg['X-Hg-Notification'] = 'changeset ' + short(node)
218 if not msg['Message-Id']:
220 if not msg['Message-Id']:
219 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
221 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
220 (short(node), int(time.time()),
222 (short(node), int(time.time()),
221 hash(self.repo.root), socket.getfqdn()))
223 hash(self.repo.root), socket.getfqdn()))
222 msg['To'] = ', '.join(self.subs)
224 msg['To'] = ', '.join(self.subs)
223
225
224 msgtext = msg.as_string(0)
226 msgtext = msg.as_string(0)
225 if self.ui.configbool('notify', 'test', True):
227 if self.ui.configbool('notify', 'test', True):
226 self.ui.write(msgtext)
228 self.ui.write(msgtext)
227 if not msgtext.endswith('\n'):
229 if not msgtext.endswith('\n'):
228 self.ui.write('\n')
230 self.ui.write('\n')
229 else:
231 else:
230 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
232 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
231 (len(self.subs), count))
233 (len(self.subs), count))
232 mail.sendmail(self.ui, templater.email(msg['From']),
234 mail.sendmail(self.ui, templater.email(msg['From']),
233 self.subs, msgtext)
235 self.subs, msgtext)
234
236
235 def diff(self, node, ref):
237 def diff(self, node, ref):
236 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
238 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
237 if maxdiff == 0:
239 if maxdiff == 0:
238 return
240 return
239 prev = self.repo.changelog.parents(node)[0]
241 prev = self.repo.changelog.parents(node)[0]
240 self.ui.pushbuffer()
242 self.ui.pushbuffer()
241 patch.diff(self.repo, prev, ref)
243 patch.diff(self.repo, prev, ref)
242 difflines = self.ui.popbuffer().splitlines(1)
244 difflines = self.ui.popbuffer().splitlines(1)
243 if self.ui.configbool('notify', 'diffstat', True):
245 if self.ui.configbool('notify', 'diffstat', True):
244 s = patch.diffstat(difflines)
246 s = patch.diffstat(difflines)
245 # s may be nil, don't include the header if it is
247 # s may be nil, don't include the header if it is
246 if s:
248 if s:
247 self.ui.write('\ndiffstat:\n\n%s' % s)
249 self.ui.write('\ndiffstat:\n\n%s' % s)
248 if maxdiff > 0 and len(difflines) > maxdiff:
250 if maxdiff > 0 and len(difflines) > maxdiff:
249 self.ui.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
251 self.ui.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
250 (len(difflines), maxdiff))
252 (len(difflines), maxdiff))
251 difflines = difflines[:maxdiff]
253 difflines = difflines[:maxdiff]
252 elif difflines:
254 elif difflines:
253 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
255 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
254 self.ui.write(*difflines)
256 self.ui.write(*difflines)
255
257
256 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
258 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
257 '''send email notifications to interested subscribers.
259 '''send email notifications to interested subscribers.
258
260
259 if used as changegroup hook, send one email for all changesets in
261 if used as changegroup hook, send one email for all changesets in
260 changegroup. else send one email per changeset.'''
262 changegroup. else send one email per changeset.'''
261 n = notifier(ui, repo, hooktype)
263 n = notifier(ui, repo, hooktype)
262 if not n.subs:
264 if not n.subs:
263 ui.debug(_('notify: no subscribers to repo %s\n') % n.root)
265 ui.debug(_('notify: no subscribers to repo %s\n') % n.root)
264 return
266 return
265 if n.skipsource(source):
267 if n.skipsource(source):
266 ui.debug(_('notify: changes have source "%s" - skipping\n') %
268 ui.debug(_('notify: changes have source "%s" - skipping\n') %
267 source)
269 source)
268 return
270 return
269 node = bin(node)
271 node = bin(node)
270 ui.pushbuffer()
272 ui.pushbuffer()
271 if hooktype == 'changegroup':
273 if hooktype == 'changegroup':
272 start = repo.changelog.rev(node)
274 start = repo.changelog.rev(node)
273 end = repo.changelog.count()
275 end = repo.changelog.count()
274 count = end - start
276 count = end - start
275 for rev in xrange(start, end):
277 for rev in xrange(start, end):
276 n.node(repo.changelog.node(rev))
278 n.node(repo.changelog.node(rev))
277 n.diff(node, repo.changelog.tip())
279 n.diff(node, repo.changelog.tip())
278 else:
280 else:
279 count = 1
281 count = 1
280 n.node(node)
282 n.node(node)
281 n.diff(node, node)
283 n.diff(node, node)
282 data = ui.popbuffer()
284 data = ui.popbuffer()
283 n.send(node, count, data)
285 n.send(node, count, data)
@@ -1,54 +1,56 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 cat <<EOF >> $HGRCPATH
3 cat <<EOF >> $HGRCPATH
4 [extensions]
4 [extensions]
5 notify=
5 notify=
6
6
7 [hooks]
7 [hooks]
8 incoming.notify = python:hgext.notify.hook
8 incoming.notify = python:hgext.notify.hook
9
9
10 [notify]
10 [notify]
11 sources = pull
11 sources = pull
12 diffstat = False
12 diffstat = False
13
13
14 [usersubs]
14 [usersubs]
15 foo@bar = *
15 foo@bar = *
16
16
17 [reposubs]
17 [reposubs]
18 * = baz
18 * = baz
19 EOF
19 EOF
20
20
21 hg help notify
21 hg help notify
22 hg init a
22 hg init a
23 echo a > a/a
23 echo a > a/a
24 echo % commit
24 echo % commit
25 hg --traceback --cwd a commit -Ama -d '0 0'
25 hg --traceback --cwd a commit -Ama -d '0 0'
26
26
27 echo % clone
27 echo % clone
28 hg --traceback clone a b
28 hg --traceback clone a b
29
29
30 echo a >> a/a
30 echo a >> a/a
31 echo % commit
31 echo % commit
32 hg --traceback --cwd a commit -Amb -d '1 0'
32 hg --traceback --cwd a commit -Amb -d '1 0'
33
33
34 echo '% pull (minimal config)'
34 echo '% pull (minimal config)'
35 hg --traceback --cwd b pull ../a 2>&1 | sed -e 's/\(Message-Id:\).*/\1/' \
35 hg --traceback --cwd b pull ../a 2>&1 | sed -e 's/\(Message-Id:\).*/\1/' \
36 -e 's/changeset \([0-9a-f]* *\)in .*test-notif/changeset \1in test-notif/' \
36 -e 's/changeset \([0-9a-f]* *\)in .*test-notif/changeset \1in test-notif/' \
37 -e 's/^details: .*test-notify/details: test-notify/'
37 -e 's/^details: .*test-notify/details: test-notify/' \
38 -e 's/^Date:.*/Date:/'
38
39
39 cat <<EOF >> $HGRCPATH
40 cat <<EOF >> $HGRCPATH
40 [notify]
41 [notify]
41 config = $HGTMP/.notify.conf
42 config = $HGTMP/.notify.conf
42 domain = test.com
43 domain = test.com
43 strip = 3
44 strip = 3
44 template = Subject: {desc|firstline|strip}\nFrom: {author}\n\nchangeset {node|short} in {webroot}\ndescription:\n\t{desc|tabindent|strip}
45 template = Subject: {desc|firstline|strip}\nFrom: {author}\n\nchangeset {node|short} in {webroot}\ndescription:\n\t{desc|tabindent|strip}
45
46
46 [web]
47 [web]
47 baseurl = http://test/
48 baseurl = http://test/
48 EOF
49 EOF
49
50
50 echo % pull
51 echo % pull
51 hg --cwd b rollback
52 hg --cwd b rollback
52 hg --traceback --cwd b pull ../a 2>&1 | sed -e 's/\(Message-Id:\).*/\1/' \
53 hg --traceback --cwd b pull ../a 2>&1 | sed -e 's/\(Message-Id:\).*/\1/' \
53 -e 's/changeset \([0-9a-f]*\) in .*/changeset \1/'
54 -e 's/changeset \([0-9a-f]*\) in .*/changeset \1/' \
55 -e 's/^Date:.*/Date:/'
54
56
@@ -1,61 +1,63 b''
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 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
7 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
8 % commit
8 % commit
9 % pull (minimal config)
9 % pull (minimal config)
10 pulling from ../a
10 pulling from ../a
11 searching for changes
11 searching for changes
12 adding changesets
12 adding changesets
13 adding manifests
13 adding manifests
14 adding file changes
14 adding file changes
15 added 1 changesets with 1 changes to 1 files
15 added 1 changesets with 1 changes to 1 files
16 Date:
16 Subject: changeset in test-notify/b: b
17 Subject: changeset in test-notify/b: b
17 From: test
18 From: test
18 X-Hg-Notification: changeset 0647d048b600
19 X-Hg-Notification: changeset 0647d048b600
19 Message-Id:
20 Message-Id:
20 To: baz, foo@bar
21 To: baz, foo@bar
21
22
22 changeset 0647d048b600 in test-notify/b
23 changeset 0647d048b600 in test-notify/b
23 details: test-notify/b?cmd=changeset;node=0647d048b600
24 details: test-notify/b?cmd=changeset;node=0647d048b600
24 description:
25 description:
25 b
26 b
26
27
27 diffs (6 lines):
28 diffs (6 lines):
28
29
29 diff -r cb9a9f314b8b -r 0647d048b600 a
30 diff -r cb9a9f314b8b -r 0647d048b600 a
30 --- a/a Thu Jan 01 00:00:00 1970 +0000
31 --- a/a Thu Jan 01 00:00:00 1970 +0000
31 +++ b/a Thu Jan 01 00:00:01 1970 +0000
32 +++ b/a Thu Jan 01 00:00:01 1970 +0000
32 @@ -1,1 +1,2 @@ a
33 @@ -1,1 +1,2 @@ a
33 a
34 a
34 +a
35 +a
35 (run 'hg update' to get a working copy)
36 (run 'hg update' to get a working copy)
36 % pull
37 % pull
37 rolling back last transaction
38 rolling back last transaction
38 pulling from ../a
39 pulling from ../a
39 searching for changes
40 searching for changes
40 adding changesets
41 adding changesets
41 adding manifests
42 adding manifests
42 adding file changes
43 adding file changes
43 added 1 changesets with 1 changes to 1 files
44 added 1 changesets with 1 changes to 1 files
45 Date:
44 Subject: b
46 Subject: b
45 From: test@test.com
47 From: test@test.com
46 X-Hg-Notification: changeset 0647d048b600
48 X-Hg-Notification: changeset 0647d048b600
47 Message-Id:
49 Message-Id:
48 To: baz@test.com, foo@bar
50 To: baz@test.com, foo@bar
49
51
50 changeset 0647d048b600
52 changeset 0647d048b600
51 description:
53 description:
52 b
54 b
53 diffs (6 lines):
55 diffs (6 lines):
54
56
55 diff -r cb9a9f314b8b -r 0647d048b600 a
57 diff -r cb9a9f314b8b -r 0647d048b600 a
56 --- a/a Thu Jan 01 00:00:00 1970 +0000
58 --- a/a Thu Jan 01 00:00:00 1970 +0000
57 +++ b/a Thu Jan 01 00:00:01 1970 +0000
59 +++ b/a Thu Jan 01 00:00:01 1970 +0000
58 @@ -1,1 +1,2 @@ a
60 @@ -1,1 +1,2 @@ a
59 a
61 a
60 +a
62 +a
61 (run 'hg update' to get a working copy)
63 (run 'hg update' to get a working copy)
General Comments 0
You need to be logged in to leave comments. Login now