##// END OF EJS Templates
dirstate: don't write repo.currenttransaction to repo.dirstate if repo...
Sietse Brouwer -
r27228:10695f8f stable
parent child Browse files
Show More
@@ -1,235 +1,236
1 1 # hook.py - hook support for mercurial
2 2 #
3 3 # Copyright 2007 Matt Mackall <mpm@selenic.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 from __future__ import absolute_import
9 9
10 10 import os
11 11 import sys
12 12 import time
13 13
14 14 from .i18n import _
15 15 from . import (
16 16 demandimport,
17 17 error,
18 18 extensions,
19 19 util,
20 20 )
21 21
22 22 def _pythonhook(ui, repo, name, hname, funcname, args, throw):
23 23 '''call python hook. hook is callable object, looked up as
24 24 name in python module. if callable returns "true", hook
25 25 fails, else passes. if hook raises exception, treated as
26 26 hook failure. exception propagates if throw is "true".
27 27
28 28 reason for "true" meaning "hook failed" is so that
29 29 unmodified commands (e.g. mercurial.commands.update) can
30 30 be run as hooks without wrappers to convert return values.'''
31 31
32 32 if callable(funcname):
33 33 obj = funcname
34 34 funcname = obj.__module__ + "." + obj.__name__
35 35 else:
36 36 d = funcname.rfind('.')
37 37 if d == -1:
38 38 raise error.HookLoadError(
39 39 _('%s hook is invalid ("%s" not in a module)')
40 40 % (hname, funcname))
41 41 modname = funcname[:d]
42 42 oldpaths = sys.path
43 43 if util.mainfrozen():
44 44 # binary installs require sys.path manipulation
45 45 modpath, modfile = os.path.split(modname)
46 46 if modpath and modfile:
47 47 sys.path = sys.path[:] + [modpath]
48 48 modname = modfile
49 49 with demandimport.deactivated():
50 50 try:
51 51 obj = __import__(modname)
52 52 except ImportError:
53 53 e1 = sys.exc_type, sys.exc_value, sys.exc_traceback
54 54 try:
55 55 # extensions are loaded with hgext_ prefix
56 56 obj = __import__("hgext_%s" % modname)
57 57 except ImportError:
58 58 e2 = sys.exc_type, sys.exc_value, sys.exc_traceback
59 59 if ui.tracebackflag:
60 60 ui.warn(_('exception from first failed import '
61 61 'attempt:\n'))
62 62 ui.traceback(e1)
63 63 if ui.tracebackflag:
64 64 ui.warn(_('exception from second failed import '
65 65 'attempt:\n'))
66 66 ui.traceback(e2)
67 67 raise error.HookLoadError(
68 68 _('%s hook is invalid (import of "%s" failed)') %
69 69 (hname, modname))
70 70 sys.path = oldpaths
71 71 try:
72 72 for p in funcname.split('.')[1:]:
73 73 obj = getattr(obj, p)
74 74 except AttributeError:
75 75 raise error.HookLoadError(
76 76 _('%s hook is invalid ("%s" is not defined)')
77 77 % (hname, funcname))
78 78 if not callable(obj):
79 79 raise error.HookLoadError(
80 80 _('%s hook is invalid ("%s" is not callable)')
81 81 % (hname, funcname))
82 82
83 83 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
84 84 starttime = time.time()
85 85
86 86 try:
87 87 # redirect IO descriptors to the ui descriptors so hooks
88 88 # that write directly to these don't mess up the command
89 89 # protocol when running through the command server
90 90 old = sys.stdout, sys.stderr, sys.stdin
91 91 sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin
92 92
93 93 r = obj(ui=ui, repo=repo, hooktype=name, **args)
94 94 except Exception as exc:
95 95 if isinstance(exc, error.Abort):
96 96 ui.warn(_('error: %s hook failed: %s\n') %
97 97 (hname, exc.args[0]))
98 98 else:
99 99 ui.warn(_('error: %s hook raised an exception: '
100 100 '%s\n') % (hname, exc))
101 101 if throw:
102 102 raise
103 103 ui.traceback()
104 104 return True, True
105 105 finally:
106 106 sys.stdout, sys.stderr, sys.stdin = old
107 107 duration = time.time() - starttime
108 108 ui.log('pythonhook', 'pythonhook-%s: %s finished in %0.2f seconds\n',
109 109 name, funcname, duration)
110 110 if r:
111 111 if throw:
112 112 raise error.HookAbort(_('%s hook failed') % hname)
113 113 ui.warn(_('warning: %s hook failed\n') % hname)
114 114 return r, False
115 115
116 116 def _exthook(ui, repo, name, cmd, args, throw):
117 117 ui.note(_("running hook %s: %s\n") % (name, cmd))
118 118
119 119 starttime = time.time()
120 120 env = {}
121 121
122 122 # make in-memory changes visible to external process
123 tr = repo.currenttransaction()
124 repo.dirstate.write(tr)
125 if tr and tr.writepending():
126 env['HG_PENDING'] = repo.root
123 if repo is not None:
124 tr = repo.currenttransaction()
125 repo.dirstate.write(tr)
126 if tr and tr.writepending():
127 env['HG_PENDING'] = repo.root
127 128
128 129 for k, v in args.iteritems():
129 130 if callable(v):
130 131 v = v()
131 132 if isinstance(v, dict):
132 133 # make the dictionary element order stable across Python
133 134 # implementations
134 135 v = ('{' +
135 136 ', '.join('%r: %r' % i for i in sorted(v.iteritems())) +
136 137 '}')
137 138 env['HG_' + k.upper()] = v
138 139
139 140 if repo:
140 141 cwd = repo.root
141 142 else:
142 143 cwd = os.getcwd()
143 144 r = ui.system(cmd, environ=env, cwd=cwd)
144 145
145 146 duration = time.time() - starttime
146 147 ui.log('exthook', 'exthook-%s: %s finished in %0.2f seconds\n',
147 148 name, cmd, duration)
148 149 if r:
149 150 desc, r = util.explainexit(r)
150 151 if throw:
151 152 raise error.HookAbort(_('%s hook %s') % (name, desc))
152 153 ui.warn(_('warning: %s hook %s\n') % (name, desc))
153 154 return r
154 155
155 156 def _allhooks(ui):
156 157 hooks = []
157 158 for name, cmd in ui.configitems('hooks'):
158 159 if not name.startswith('priority'):
159 160 priority = ui.configint('hooks', 'priority.%s' % name, 0)
160 161 hooks.append((-priority, len(hooks), name, cmd))
161 162 return [(k, v) for p, o, k, v in sorted(hooks)]
162 163
163 164 _redirect = False
164 165 def redirect(state):
165 166 global _redirect
166 167 _redirect = state
167 168
168 169 def hook(ui, repo, name, throw=False, **args):
169 170 if not ui.callhooks:
170 171 return False
171 172
172 173 hooks = []
173 174 for hname, cmd in _allhooks(ui):
174 175 if hname.split('.')[0] == name and cmd:
175 176 hooks.append((hname, cmd))
176 177
177 178 res = runhooks(ui, repo, name, hooks, throw=throw, **args)
178 179 r = False
179 180 for hname, cmd in hooks:
180 181 r = res[hname][0] or r
181 182 return r
182 183
183 184 def runhooks(ui, repo, name, hooks, throw=False, **args):
184 185 res = {}
185 186 oldstdout = -1
186 187
187 188 try:
188 189 for hname, cmd in hooks:
189 190 if oldstdout == -1 and _redirect:
190 191 try:
191 192 stdoutno = sys.__stdout__.fileno()
192 193 stderrno = sys.__stderr__.fileno()
193 194 # temporarily redirect stdout to stderr, if possible
194 195 if stdoutno >= 0 and stderrno >= 0:
195 196 sys.__stdout__.flush()
196 197 oldstdout = os.dup(stdoutno)
197 198 os.dup2(stderrno, stdoutno)
198 199 except (OSError, AttributeError):
199 200 # files seem to be bogus, give up on redirecting (WSGI, etc)
200 201 pass
201 202
202 203 if callable(cmd):
203 204 r, raised = _pythonhook(ui, repo, name, hname, cmd, args, throw)
204 205 elif cmd.startswith('python:'):
205 206 if cmd.count(':') >= 2:
206 207 path, cmd = cmd[7:].rsplit(':', 1)
207 208 path = util.expandpath(path)
208 209 if repo:
209 210 path = os.path.join(repo.root, path)
210 211 try:
211 212 mod = extensions.loadpath(path, 'hghook.%s' % hname)
212 213 except Exception:
213 214 ui.write(_("loading %s hook failed:\n") % hname)
214 215 raise
215 216 hookfn = getattr(mod, cmd)
216 217 else:
217 218 hookfn = cmd[7:].strip()
218 219 r, raised = _pythonhook(ui, repo, name, hname, hookfn, args,
219 220 throw)
220 221 else:
221 222 r = _exthook(ui, repo, hname, cmd, args, throw)
222 223 raised = False
223 224
224 225 res[hname] = r, raised
225 226
226 227 # The stderr is fully buffered on Windows when connected to a pipe.
227 228 # A forcible flush is required to make small stderr data in the
228 229 # remote side available to the client immediately.
229 230 sys.stderr.flush()
230 231 finally:
231 232 if _redirect and oldstdout >= 0:
232 233 os.dup2(oldstdout, stdoutno)
233 234 os.close(oldstdout)
234 235
235 236 return res
@@ -1,717 +1,726
1 1 commit hooks can see env vars
2 2 (and post-transaction one are run unlocked)
3 3
4 4 $ cat << EOF >> $HGRCPATH
5 5 > [experimental]
6 6 > # drop me once bundle2 is the default,
7 7 > # added to get test change early.
8 8 > bundle2-exp = True
9 9 > EOF
10 10
11 11 $ cat > $TESTTMP/txnabort.checkargs.py <<EOF
12 12 > def showargs(ui, repo, hooktype, **kwargs):
13 13 > ui.write('%s python hook: %s\n' % (hooktype, ','.join(sorted(kwargs))))
14 14 > EOF
15 15
16 16 $ hg init a
17 17 $ cd a
18 18 $ cat > .hg/hgrc <<EOF
19 19 > [hooks]
20 20 > commit = sh -c "HG_LOCAL= HG_TAG= printenv.py commit"
21 21 > commit.b = sh -c "HG_LOCAL= HG_TAG= printenv.py commit.b"
22 22 > precommit = sh -c "HG_LOCAL= HG_NODE= HG_TAG= printenv.py precommit"
23 23 > pretxncommit = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxncommit"
24 24 > pretxncommit.tip = hg -q tip
25 25 > pre-identify = printenv.py pre-identify 1
26 26 > pre-cat = printenv.py pre-cat
27 27 > post-cat = printenv.py post-cat
28 28 > pretxnopen = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxnopen"
29 29 > pretxnclose = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxnclose"
30 30 > txnclose = sh -c "HG_LOCAL= HG_TAG= printenv.py txnclose"
31 31 > txnabort.0 = python:$TESTTMP/txnabort.checkargs.py:showargs
32 32 > txnabort.1 = sh -c "HG_LOCAL= HG_TAG= printenv.py txnabort"
33 33 > txnclose.checklock = sh -c "hg debuglock > /dev/null"
34 34 > EOF
35 35 $ echo a > a
36 36 $ hg add a
37 37 $ hg commit -m a
38 38 precommit hook: HG_PARENT1=0000000000000000000000000000000000000000
39 39 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
40 40 pretxncommit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000 HG_PENDING=$TESTTMP/a
41 41 0:cb9a9f314b8b
42 42 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
43 43 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
44 44 commit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
45 45 commit.b hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
46 46
47 47 $ hg clone . ../b
48 48 updating to branch default
49 49 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
50 50 $ cd ../b
51 51
52 52 changegroup hooks can see env vars
53 53
54 54 $ cat > .hg/hgrc <<EOF
55 55 > [hooks]
56 56 > prechangegroup = printenv.py prechangegroup
57 57 > changegroup = printenv.py changegroup
58 58 > incoming = printenv.py incoming
59 59 > EOF
60 60
61 61 pretxncommit and commit hooks can see both parents of merge
62 62
63 63 $ cd ../a
64 64 $ echo b >> a
65 65 $ hg commit -m a1 -d "1 0"
66 66 precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
67 67 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
68 68 pretxncommit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
69 69 1:ab228980c14d
70 70 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
71 71 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
72 72 commit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
73 73 commit.b hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
74 74 $ hg update -C 0
75 75 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
76 76 $ echo b > b
77 77 $ hg add b
78 78 $ hg commit -m b -d '1 0'
79 79 precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
80 80 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
81 81 pretxncommit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
82 82 2:ee9deb46ab31
83 83 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
84 84 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
85 85 commit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
86 86 commit.b hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
87 87 created new head
88 88 $ hg merge 1
89 89 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
90 90 (branch merge, don't forget to commit)
91 91 $ hg commit -m merge -d '2 0'
92 92 precommit hook: HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
93 93 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
94 94 pretxncommit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd HG_PENDING=$TESTTMP/a
95 95 3:07f3376c1e65
96 96 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
97 97 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
98 98 commit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
99 99 commit.b hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
100 100
101 101 test generic hooks
102 102
103 103 $ hg id
104 104 pre-identify hook: HG_ARGS=id HG_OPTS={'bookmarks': None, 'branch': None, 'id': None, 'insecure': None, 'num': None, 'remotecmd': '', 'rev': '', 'ssh': '', 'tags': None} HG_PATS=[]
105 105 abort: pre-identify hook exited with status 1
106 106 [255]
107 107 $ hg cat b
108 108 pre-cat hook: HG_ARGS=cat b HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b']
109 109 b
110 110 post-cat hook: HG_ARGS=cat b HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b'] HG_RESULT=0
111 111
112 112 $ cd ../b
113 113 $ hg pull ../a
114 114 pulling from ../a
115 115 searching for changes
116 116 prechangegroup hook: HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
117 117 adding changesets
118 118 adding manifests
119 119 adding file changes
120 120 added 3 changesets with 2 changes to 2 files
121 121 changegroup hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
122 122 incoming hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
123 123 incoming hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
124 124 incoming hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
125 125 (run 'hg update' to get a working copy)
126 126
127 127 tag hooks can see env vars
128 128
129 129 $ cd ../a
130 130 $ cat >> .hg/hgrc <<EOF
131 131 > pretag = printenv.py pretag
132 132 > tag = sh -c "HG_PARENT1= HG_PARENT2= printenv.py tag"
133 133 > EOF
134 134 $ hg tag -d '3 0' a
135 135 pretag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
136 136 precommit hook: HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
137 137 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
138 138 pretxncommit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PENDING=$TESTTMP/a
139 139 4:539e4b31b6dc
140 140 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
141 141 tag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
142 142 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
143 143 commit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
144 144 commit.b hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
145 145 $ hg tag -l la
146 146 pretag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
147 147 tag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
148 148
149 149 pretag hook can forbid tagging
150 150
151 151 $ echo "pretag.forbid = printenv.py pretag.forbid 1" >> .hg/hgrc
152 152 $ hg tag -d '4 0' fa
153 153 pretag hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
154 154 pretag.forbid hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
155 155 abort: pretag.forbid hook exited with status 1
156 156 [255]
157 157 $ hg tag -l fla
158 158 pretag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
159 159 pretag.forbid hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
160 160 abort: pretag.forbid hook exited with status 1
161 161 [255]
162 162
163 163 pretxncommit hook can see changeset, can roll back txn, changeset no
164 164 more there after
165 165
166 166 $ echo "pretxncommit.forbid0 = hg tip -q" >> .hg/hgrc
167 167 $ echo "pretxncommit.forbid1 = printenv.py pretxncommit.forbid 1" >> .hg/hgrc
168 168 $ echo z > z
169 169 $ hg add z
170 170 $ hg -q tip
171 171 4:539e4b31b6dc
172 172 $ hg commit -m 'fail' -d '4 0'
173 173 precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
174 174 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
175 175 pretxncommit hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
176 176 5:6f611f8018c1
177 177 5:6f611f8018c1
178 178 pretxncommit.forbid hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
179 179 transaction abort!
180 180 txnabort python hook: txnid,txnname
181 181 txnabort hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
182 182 rollback completed
183 183 abort: pretxncommit.forbid1 hook exited with status 1
184 184 [255]
185 185 $ hg -q tip
186 186 4:539e4b31b6dc
187 187
188 188 (Check that no 'changelog.i.a' file were left behind)
189 189
190 190 $ ls -1 .hg/store/
191 191 00changelog.i
192 192 00manifest.i
193 193 data
194 194 fncache
195 195 journal.phaseroots
196 196 phaseroots
197 197 undo
198 198 undo.backup.fncache
199 199 undo.backupfiles
200 200 undo.phaseroots
201 201
202 202
203 203 precommit hook can prevent commit
204 204
205 205 $ echo "precommit.forbid = printenv.py precommit.forbid 1" >> .hg/hgrc
206 206 $ hg commit -m 'fail' -d '4 0'
207 207 precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
208 208 precommit.forbid hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
209 209 abort: precommit.forbid hook exited with status 1
210 210 [255]
211 211 $ hg -q tip
212 212 4:539e4b31b6dc
213 213
214 214 preupdate hook can prevent update
215 215
216 216 $ echo "preupdate = printenv.py preupdate" >> .hg/hgrc
217 217 $ hg update 1
218 218 preupdate hook: HG_PARENT1=ab228980c14d
219 219 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
220 220
221 221 update hook
222 222
223 223 $ echo "update = printenv.py update" >> .hg/hgrc
224 224 $ hg update
225 225 preupdate hook: HG_PARENT1=539e4b31b6dc
226 226 update hook: HG_ERROR=0 HG_PARENT1=539e4b31b6dc
227 227 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
228 228
229 229 pushkey hook
230 230
231 231 $ echo "pushkey = printenv.py pushkey" >> .hg/hgrc
232 232 $ cd ../b
233 233 $ hg bookmark -r null foo
234 234 $ hg push -B foo ../a
235 235 pushing to ../a
236 236 searching for changes
237 237 no changes found
238 238 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=push (glob)
239 239 pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_PENDING=$TESTTMP/a HG_SOURCE=push HG_TXNID=TXN:* HG_TXNNAME=push HG_URL=push (glob)
240 240 pushkey hook: HG_KEY=foo HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_RET=1
241 241 txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_SOURCE=push HG_TXNID=TXN:* HG_TXNNAME=push HG_URL=push (glob)
242 242 exporting bookmark foo
243 243 [1]
244 244 $ cd ../a
245 245
246 246 listkeys hook
247 247
248 248 $ echo "listkeys = printenv.py listkeys" >> .hg/hgrc
249 249 $ hg bookmark -r null bar
250 250 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
251 251 pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
252 252 txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
253 253 $ cd ../b
254 254 $ hg pull -B bar ../a
255 255 pulling from ../a
256 256 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
257 257 no changes found
258 258 listkeys hook: HG_NAMESPACE=phase HG_VALUES={}
259 259 adding remote bookmark bar
260 260 listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
261 261 $ cd ../a
262 262
263 263 test that prepushkey can prevent incoming keys
264 264
265 265 $ echo "prepushkey = printenv.py prepushkey.forbid 1" >> .hg/hgrc
266 266 $ cd ../b
267 267 $ hg bookmark -r null baz
268 268 $ hg push -B baz ../a
269 269 pushing to ../a
270 270 searching for changes
271 271 listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
272 272 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
273 273 no changes found
274 274 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=push (glob)
275 275 prepushkey.forbid hook: HG_BUNDLE2=1 HG_KEY=baz HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_SOURCE=push HG_TXNID=TXN:* HG_URL=push (glob)
276 276 pushkey-abort: prepushkey hook exited with status 1
277 277 abort: exporting bookmark baz failed!
278 278 [255]
279 279 $ cd ../a
280 280
281 281 test that prelistkeys can prevent listing keys
282 282
283 283 $ echo "prelistkeys = printenv.py prelistkeys.forbid 1" >> .hg/hgrc
284 284 $ hg bookmark -r null quux
285 285 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
286 286 pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
287 287 txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
288 288 $ cd ../b
289 289 $ hg pull -B quux ../a
290 290 pulling from ../a
291 291 prelistkeys.forbid hook: HG_NAMESPACE=bookmarks
292 292 abort: prelistkeys hook exited with status 1
293 293 [255]
294 294 $ cd ../a
295 295 $ rm .hg/hgrc
296 296
297 297 prechangegroup hook can prevent incoming changes
298 298
299 299 $ cd ../b
300 300 $ hg -q tip
301 301 3:07f3376c1e65
302 302 $ cat > .hg/hgrc <<EOF
303 303 > [hooks]
304 304 > prechangegroup.forbid = printenv.py prechangegroup.forbid 1
305 305 > EOF
306 306 $ hg pull ../a
307 307 pulling from ../a
308 308 searching for changes
309 309 prechangegroup.forbid hook: HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
310 310 abort: prechangegroup.forbid hook exited with status 1
311 311 [255]
312 312
313 313 pretxnchangegroup hook can see incoming changes, can roll back txn,
314 314 incoming changes no longer there after
315 315
316 316 $ cat > .hg/hgrc <<EOF
317 317 > [hooks]
318 318 > pretxnchangegroup.forbid0 = hg tip -q
319 319 > pretxnchangegroup.forbid1 = printenv.py pretxnchangegroup.forbid 1
320 320 > EOF
321 321 $ hg pull ../a
322 322 pulling from ../a
323 323 searching for changes
324 324 adding changesets
325 325 adding manifests
326 326 adding file changes
327 327 added 1 changesets with 1 changes to 1 files
328 328 4:539e4b31b6dc
329 329 pretxnchangegroup.forbid hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/b HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
330 330 transaction abort!
331 331 rollback completed
332 332 abort: pretxnchangegroup.forbid1 hook exited with status 1
333 333 [255]
334 334 $ hg -q tip
335 335 3:07f3376c1e65
336 336
337 337 outgoing hooks can see env vars
338 338
339 339 $ rm .hg/hgrc
340 340 $ cat > ../a/.hg/hgrc <<EOF
341 341 > [hooks]
342 342 > preoutgoing = printenv.py preoutgoing
343 343 > outgoing = printenv.py outgoing
344 344 > EOF
345 345 $ hg pull ../a
346 346 pulling from ../a
347 347 searching for changes
348 348 preoutgoing hook: HG_SOURCE=pull
349 349 outgoing hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_SOURCE=pull
350 350 adding changesets
351 351 adding manifests
352 352 adding file changes
353 353 added 1 changesets with 1 changes to 1 files
354 354 adding remote bookmark quux
355 355 (run 'hg update' to get a working copy)
356 356 $ hg rollback
357 357 repository tip rolled back to revision 3 (undo pull)
358 358
359 359 preoutgoing hook can prevent outgoing changes
360 360
361 361 $ echo "preoutgoing.forbid = printenv.py preoutgoing.forbid 1" >> ../a/.hg/hgrc
362 362 $ hg pull ../a
363 363 pulling from ../a
364 364 searching for changes
365 365 preoutgoing hook: HG_SOURCE=pull
366 366 preoutgoing.forbid hook: HG_SOURCE=pull
367 367 abort: preoutgoing.forbid hook exited with status 1
368 368 [255]
369 369
370 370 outgoing hooks work for local clones
371 371
372 372 $ cd ..
373 373 $ cat > a/.hg/hgrc <<EOF
374 374 > [hooks]
375 375 > preoutgoing = printenv.py preoutgoing
376 376 > outgoing = printenv.py outgoing
377 377 > EOF
378 378 $ hg clone a c
379 379 preoutgoing hook: HG_SOURCE=clone
380 380 outgoing hook: HG_NODE=0000000000000000000000000000000000000000 HG_SOURCE=clone
381 381 updating to branch default
382 382 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
383 383 $ rm -rf c
384 384
385 385 preoutgoing hook can prevent outgoing changes for local clones
386 386
387 387 $ echo "preoutgoing.forbid = printenv.py preoutgoing.forbid 1" >> a/.hg/hgrc
388 388 $ hg clone a zzz
389 389 preoutgoing hook: HG_SOURCE=clone
390 390 preoutgoing.forbid hook: HG_SOURCE=clone
391 391 abort: preoutgoing.forbid hook exited with status 1
392 392 [255]
393 393
394 394 $ cd "$TESTTMP/b"
395 395
396 396 $ cat > hooktests.py <<EOF
397 397 > from mercurial import error
398 398 >
399 399 > uncallable = 0
400 400 >
401 401 > def printargs(args):
402 402 > args.pop('ui', None)
403 403 > args.pop('repo', None)
404 404 > a = list(args.items())
405 405 > a.sort()
406 406 > print 'hook args:'
407 407 > for k, v in a:
408 408 > print ' ', k, v
409 409 >
410 410 > def passhook(**args):
411 411 > printargs(args)
412 412 >
413 413 > def failhook(**args):
414 414 > printargs(args)
415 415 > return True
416 416 >
417 417 > class LocalException(Exception):
418 418 > pass
419 419 >
420 420 > def raisehook(**args):
421 421 > raise LocalException('exception from hook')
422 422 >
423 423 > def aborthook(**args):
424 424 > raise error.Abort('raise abort from hook')
425 425 >
426 426 > def brokenhook(**args):
427 427 > return 1 + {}
428 428 >
429 429 > def verbosehook(ui, **args):
430 430 > ui.note('verbose output from hook\n')
431 431 >
432 432 > def printtags(ui, repo, **args):
433 433 > print sorted(repo.tags())
434 434 >
435 435 > class container:
436 436 > unreachable = 1
437 437 > EOF
438 438
439 439 test python hooks
440 440
441 441 #if windows
442 442 $ PYTHONPATH="$TESTTMP/b;$PYTHONPATH"
443 443 #else
444 444 $ PYTHONPATH="$TESTTMP/b:$PYTHONPATH"
445 445 #endif
446 446 $ export PYTHONPATH
447 447
448 448 $ echo '[hooks]' > ../a/.hg/hgrc
449 449 $ echo 'preoutgoing.broken = python:hooktests.brokenhook' >> ../a/.hg/hgrc
450 450 $ hg pull ../a 2>&1 | grep 'raised an exception'
451 451 error: preoutgoing.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
452 452
453 453 $ echo '[hooks]' > ../a/.hg/hgrc
454 454 $ echo 'preoutgoing.raise = python:hooktests.raisehook' >> ../a/.hg/hgrc
455 455 $ hg pull ../a 2>&1 | grep 'raised an exception'
456 456 error: preoutgoing.raise hook raised an exception: exception from hook
457 457
458 458 $ echo '[hooks]' > ../a/.hg/hgrc
459 459 $ echo 'preoutgoing.abort = python:hooktests.aborthook' >> ../a/.hg/hgrc
460 460 $ hg pull ../a
461 461 pulling from ../a
462 462 searching for changes
463 463 error: preoutgoing.abort hook failed: raise abort from hook
464 464 abort: raise abort from hook
465 465 [255]
466 466
467 467 $ echo '[hooks]' > ../a/.hg/hgrc
468 468 $ echo 'preoutgoing.fail = python:hooktests.failhook' >> ../a/.hg/hgrc
469 469 $ hg pull ../a
470 470 pulling from ../a
471 471 searching for changes
472 472 hook args:
473 473 hooktype preoutgoing
474 474 source pull
475 475 abort: preoutgoing.fail hook failed
476 476 [255]
477 477
478 478 $ echo '[hooks]' > ../a/.hg/hgrc
479 479 $ echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc
480 480 $ hg pull ../a
481 481 pulling from ../a
482 482 searching for changes
483 483 abort: preoutgoing.uncallable hook is invalid ("hooktests.uncallable" is not callable)
484 484 [255]
485 485
486 486 $ echo '[hooks]' > ../a/.hg/hgrc
487 487 $ echo 'preoutgoing.nohook = python:hooktests.nohook' >> ../a/.hg/hgrc
488 488 $ hg pull ../a
489 489 pulling from ../a
490 490 searching for changes
491 491 abort: preoutgoing.nohook hook is invalid ("hooktests.nohook" is not defined)
492 492 [255]
493 493
494 494 $ echo '[hooks]' > ../a/.hg/hgrc
495 495 $ echo 'preoutgoing.nomodule = python:nomodule' >> ../a/.hg/hgrc
496 496 $ hg pull ../a
497 497 pulling from ../a
498 498 searching for changes
499 499 abort: preoutgoing.nomodule hook is invalid ("nomodule" not in a module)
500 500 [255]
501 501
502 502 $ echo '[hooks]' > ../a/.hg/hgrc
503 503 $ echo 'preoutgoing.badmodule = python:nomodule.nowhere' >> ../a/.hg/hgrc
504 504 $ hg pull ../a
505 505 pulling from ../a
506 506 searching for changes
507 507 abort: preoutgoing.badmodule hook is invalid (import of "nomodule" failed)
508 508 [255]
509 509
510 510 $ echo '[hooks]' > ../a/.hg/hgrc
511 511 $ echo 'preoutgoing.unreachable = python:hooktests.container.unreachable' >> ../a/.hg/hgrc
512 512 $ hg pull ../a
513 513 pulling from ../a
514 514 searching for changes
515 515 abort: preoutgoing.unreachable hook is invalid (import of "hooktests.container" failed)
516 516 [255]
517 517
518 518 $ echo '[hooks]' > ../a/.hg/hgrc
519 519 $ echo 'preoutgoing.pass = python:hooktests.passhook' >> ../a/.hg/hgrc
520 520 $ hg pull ../a
521 521 pulling from ../a
522 522 searching for changes
523 523 hook args:
524 524 hooktype preoutgoing
525 525 source pull
526 526 adding changesets
527 527 adding manifests
528 528 adding file changes
529 529 added 1 changesets with 1 changes to 1 files
530 530 adding remote bookmark quux
531 531 (run 'hg update' to get a working copy)
532 532
533 533 make sure --traceback works
534 534
535 535 $ echo '[hooks]' > .hg/hgrc
536 536 $ echo 'commit.abort = python:hooktests.aborthook' >> .hg/hgrc
537 537
538 538 $ echo aa > a
539 539 $ hg --traceback commit -d '0 0' -ma 2>&1 | grep '^Traceback'
540 540 Traceback (most recent call last):
541 541
542 542 $ cd ..
543 543 $ hg init c
544 544 $ cd c
545 545
546 546 $ cat > hookext.py <<EOF
547 547 > def autohook(**args):
548 548 > print "Automatically installed hook"
549 549 >
550 550 > def reposetup(ui, repo):
551 551 > repo.ui.setconfig("hooks", "commit.auto", autohook)
552 552 > EOF
553 553 $ echo '[extensions]' >> .hg/hgrc
554 554 $ echo 'hookext = hookext.py' >> .hg/hgrc
555 555
556 556 $ touch foo
557 557 $ hg add foo
558 558 $ hg ci -d '0 0' -m 'add foo'
559 559 Automatically installed hook
560 560 $ echo >> foo
561 561 $ hg ci --debug -d '0 0' -m 'change foo'
562 562 committing files:
563 563 foo
564 564 committing manifest
565 565 committing changelog
566 566 calling hook commit.auto: hgext_hookext.autohook
567 567 Automatically installed hook
568 568 committed changeset 1:52998019f6252a2b893452765fcb0a47351a5708
569 569
570 570 $ hg showconfig hooks
571 571 hooks.commit.auto=<function autohook at *> (glob)
572 572
573 573 test python hook configured with python:[file]:[hook] syntax
574 574
575 575 $ cd ..
576 576 $ mkdir d
577 577 $ cd d
578 578 $ hg init repo
579 579 $ mkdir hooks
580 580
581 581 $ cd hooks
582 582 $ cat > testhooks.py <<EOF
583 583 > def testhook(**args):
584 584 > print 'hook works'
585 585 > EOF
586 586 $ echo '[hooks]' > ../repo/.hg/hgrc
587 587 $ echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc
588 588
589 589 $ cd ../repo
590 590 $ hg commit -d '0 0'
591 591 hook works
592 592 nothing changed
593 593 [1]
594 594
595 595 $ echo '[hooks]' > .hg/hgrc
596 596 $ echo "update.ne = python:`pwd`/nonexistent.py:testhook" >> .hg/hgrc
597 597 $ echo "pre-identify.npmd = python:`pwd`/:no_python_module_dir" >> .hg/hgrc
598 598
599 599 $ hg up null
600 600 loading update.ne hook failed:
601 601 abort: No such file or directory: $TESTTMP/d/repo/nonexistent.py
602 602 [255]
603 603
604 604 $ hg id
605 605 loading pre-identify.npmd hook failed:
606 606 abort: No module named repo!
607 607 [255]
608 608
609 609 $ cd ../../b
610 610
611 611 make sure --traceback works on hook import failure
612 612
613 613 $ cat > importfail.py <<EOF
614 614 > import somebogusmodule
615 615 > # dereference something in the module to force demandimport to load it
616 616 > somebogusmodule.whatever
617 617 > EOF
618 618
619 619 $ echo '[hooks]' > .hg/hgrc
620 620 $ echo 'precommit.importfail = python:importfail.whatever' >> .hg/hgrc
621 621
622 622 $ echo a >> a
623 623 $ hg --traceback commit -ma 2>&1 | egrep -v '^( +File| [a-zA-Z(])'
624 624 exception from first failed import attempt:
625 625 Traceback (most recent call last):
626 626 ImportError: No module named somebogusmodule
627 627 exception from second failed import attempt:
628 628 Traceback (most recent call last):
629 629 ImportError: No module named hgext_importfail
630 630 Traceback (most recent call last):
631 631 HookLoadError: precommit.importfail hook is invalid (import of "importfail" failed)
632 632 abort: precommit.importfail hook is invalid (import of "importfail" failed)
633 633
634 634 Issue1827: Hooks Update & Commit not completely post operation
635 635
636 636 commit and update hooks should run after command completion. The largefiles
637 637 use demonstrates a recursive wlock, showing the hook doesn't run until the
638 638 final release (and dirstate flush).
639 639
640 640 $ echo '[hooks]' > .hg/hgrc
641 641 $ echo 'commit = hg id' >> .hg/hgrc
642 642 $ echo 'update = hg id' >> .hg/hgrc
643 643 $ echo bb > a
644 644 $ hg ci -ma
645 645 223eafe2750c tip
646 646 $ hg up 0 --config extensions.largefiles=
647 647 cb9a9f314b8b
648 648 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
649 649
650 650 make sure --verbose (and --quiet/--debug etc.) are propagated to the local ui
651 651 that is passed to pre/post hooks
652 652
653 653 $ echo '[hooks]' > .hg/hgrc
654 654 $ echo 'pre-identify = python:hooktests.verbosehook' >> .hg/hgrc
655 655 $ hg id
656 656 cb9a9f314b8b
657 657 $ hg id --verbose
658 658 calling hook pre-identify: hooktests.verbosehook
659 659 verbose output from hook
660 660 cb9a9f314b8b
661 661
662 662 Ensure hooks can be prioritized
663 663
664 664 $ echo '[hooks]' > .hg/hgrc
665 665 $ echo 'pre-identify.a = python:hooktests.verbosehook' >> .hg/hgrc
666 666 $ echo 'pre-identify.b = python:hooktests.verbosehook' >> .hg/hgrc
667 667 $ echo 'priority.pre-identify.b = 1' >> .hg/hgrc
668 668 $ echo 'pre-identify.c = python:hooktests.verbosehook' >> .hg/hgrc
669 669 $ hg id --verbose
670 670 calling hook pre-identify.b: hooktests.verbosehook
671 671 verbose output from hook
672 672 calling hook pre-identify.a: hooktests.verbosehook
673 673 verbose output from hook
674 674 calling hook pre-identify.c: hooktests.verbosehook
675 675 verbose output from hook
676 676 cb9a9f314b8b
677 677
678 678 new tags must be visible in pretxncommit (issue3210)
679 679
680 680 $ echo 'pretxncommit.printtags = python:hooktests.printtags' >> .hg/hgrc
681 681 $ hg tag -f foo
682 682 ['a', 'foo', 'tip']
683 683
684 post-init hooks must not crash (issue4983)
685 This also creates the `to` repo for the next test block.
686
687 $ cd ..
688 $ cat << EOF >> hgrc-with-post-init-hook
689 > [hooks]
690 > post-init = printenv.py post-init
691 > EOF
692 $ HGRCPATH=hgrc-with-post-init-hook hg init to
693 post-init hook: HG_ARGS=init to HG_OPTS={'insecure': None, 'remotecmd': '', 'ssh': ''} HG_PATS=['to'] HG_RESULT=0
694
684 695 new commits must be visible in pretxnchangegroup (issue3428)
685 696
686 $ cd ..
687 $ hg init to
688 697 $ echo '[hooks]' >> to/.hg/hgrc
689 698 $ echo 'prechangegroup = hg --traceback tip' >> to/.hg/hgrc
690 699 $ echo 'pretxnchangegroup = hg --traceback tip' >> to/.hg/hgrc
691 700 $ echo a >> to/a
692 701 $ hg --cwd to ci -Ama
693 702 adding a
694 703 $ hg clone to from
695 704 updating to branch default
696 705 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
697 706 $ echo aa >> from/a
698 707 $ hg --cwd from ci -mb
699 708 $ hg --cwd from push
700 709 pushing to $TESTTMP/to (glob)
701 710 searching for changes
702 711 changeset: 0:cb9a9f314b8b
703 712 tag: tip
704 713 user: test
705 714 date: Thu Jan 01 00:00:00 1970 +0000
706 715 summary: a
707 716
708 717 adding changesets
709 718 adding manifests
710 719 adding file changes
711 720 added 1 changesets with 1 changes to 1 files
712 721 changeset: 1:9836a07b9b9d
713 722 tag: tip
714 723 user: test
715 724 date: Thu Jan 01 00:00:00 1970 +0000
716 725 summary: b
717 726
General Comments 0
You need to be logged in to leave comments. Login now