##// END OF EJS Templates
changelog: register changelog.i.a as a temporary file...
Pierre-Yves David -
r23292:e44399c4 default
parent child Browse files
Show More
@@ -1,376 +1,380 b''
1 1 # changelog.py - changelog class for mercurial
2 2 #
3 3 # Copyright 2005-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 node import bin, hex, nullid
9 9 from i18n import _
10 10 import util, error, revlog, encoding
11 11
12 12 _defaultextra = {'branch': 'default'}
13 13
14 14 def _string_escape(text):
15 15 """
16 16 >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
17 17 >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
18 18 >>> s
19 19 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
20 20 >>> res = _string_escape(s)
21 21 >>> s == res.decode('string_escape')
22 22 True
23 23 """
24 24 # subset of the string_escape codec
25 25 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
26 26 return text.replace('\0', '\\0')
27 27
28 28 def decodeextra(text):
29 29 """
30 30 >>> sorted(decodeextra(encodeextra({'foo': 'bar', 'baz': chr(0) + '2'})
31 31 ... ).iteritems())
32 32 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
33 33 >>> sorted(decodeextra(encodeextra({'foo': 'bar',
34 34 ... 'baz': chr(92) + chr(0) + '2'})
35 35 ... ).iteritems())
36 36 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
37 37 """
38 38 extra = _defaultextra.copy()
39 39 for l in text.split('\0'):
40 40 if l:
41 41 if '\\0' in l:
42 42 # fix up \0 without getting into trouble with \\0
43 43 l = l.replace('\\\\', '\\\\\n')
44 44 l = l.replace('\\0', '\0')
45 45 l = l.replace('\n', '')
46 46 k, v = l.decode('string_escape').split(':', 1)
47 47 extra[k] = v
48 48 return extra
49 49
50 50 def encodeextra(d):
51 51 # keys must be sorted to produce a deterministic changelog entry
52 52 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
53 53 return "\0".join(items)
54 54
55 55 def stripdesc(desc):
56 56 """strip trailing whitespace and leading and trailing empty lines"""
57 57 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
58 58
59 59 class appender(object):
60 60 '''the changelog index must be updated last on disk, so we use this class
61 61 to delay writes to it'''
62 62 def __init__(self, vfs, name, mode, buf):
63 63 self.data = buf
64 64 fp = vfs(name, mode)
65 65 self.fp = fp
66 66 self.offset = fp.tell()
67 67 self.size = vfs.fstat(fp).st_size
68 68
69 69 def end(self):
70 70 return self.size + len("".join(self.data))
71 71 def tell(self):
72 72 return self.offset
73 73 def flush(self):
74 74 pass
75 75 def close(self):
76 76 self.fp.close()
77 77
78 78 def seek(self, offset, whence=0):
79 79 '''virtual file offset spans real file and data'''
80 80 if whence == 0:
81 81 self.offset = offset
82 82 elif whence == 1:
83 83 self.offset += offset
84 84 elif whence == 2:
85 85 self.offset = self.end() + offset
86 86 if self.offset < self.size:
87 87 self.fp.seek(self.offset)
88 88
89 89 def read(self, count=-1):
90 90 '''only trick here is reads that span real file and data'''
91 91 ret = ""
92 92 if self.offset < self.size:
93 93 s = self.fp.read(count)
94 94 ret = s
95 95 self.offset += len(s)
96 96 if count > 0:
97 97 count -= len(s)
98 98 if count != 0:
99 99 doff = self.offset - self.size
100 100 self.data.insert(0, "".join(self.data))
101 101 del self.data[1:]
102 102 s = self.data[0][doff:doff + count]
103 103 self.offset += len(s)
104 104 ret += s
105 105 return ret
106 106
107 107 def write(self, s):
108 108 self.data.append(str(s))
109 109 self.offset += len(s)
110 110
111 111 def _divertopener(opener, target):
112 112 """build an opener that writes in 'target.a' instead of 'target'"""
113 113 def _divert(name, mode='r'):
114 114 if name != target:
115 115 return opener(name, mode)
116 116 return opener(name + ".a", mode)
117 117 return _divert
118 118
119 119 def _delayopener(opener, target, buf):
120 120 """build an opener that stores chunks in 'buf' instead of 'target'"""
121 121 def _delay(name, mode='r'):
122 122 if name != target:
123 123 return opener(name, mode)
124 124 return appender(opener, name, mode, buf)
125 125 return _delay
126 126
127 127 class changelog(revlog.revlog):
128 128 def __init__(self, opener):
129 129 revlog.revlog.__init__(self, opener, "00changelog.i")
130 130 if self._initempty:
131 131 # changelogs don't benefit from generaldelta
132 132 self.version &= ~revlog.REVLOGGENERALDELTA
133 133 self._generaldelta = False
134 134 self._realopener = opener
135 135 self._delayed = False
136 136 self._delaybuf = None
137 137 self._divert = False
138 138 self.filteredrevs = frozenset()
139 139
140 140 def tip(self):
141 141 """filtered version of revlog.tip"""
142 142 for i in xrange(len(self) -1, -2, -1):
143 143 if i not in self.filteredrevs:
144 144 return self.node(i)
145 145
146 146 def __iter__(self):
147 147 """filtered version of revlog.__iter__"""
148 148 if len(self.filteredrevs) == 0:
149 149 return revlog.revlog.__iter__(self)
150 150
151 151 def filterediter():
152 152 for i in xrange(len(self)):
153 153 if i not in self.filteredrevs:
154 154 yield i
155 155
156 156 return filterediter()
157 157
158 158 def revs(self, start=0, stop=None):
159 159 """filtered version of revlog.revs"""
160 160 for i in super(changelog, self).revs(start, stop):
161 161 if i not in self.filteredrevs:
162 162 yield i
163 163
164 164 @util.propertycache
165 165 def nodemap(self):
166 166 # XXX need filtering too
167 167 self.rev(self.node(0))
168 168 return self._nodecache
169 169
170 170 def hasnode(self, node):
171 171 """filtered version of revlog.hasnode"""
172 172 try:
173 173 i = self.rev(node)
174 174 return i not in self.filteredrevs
175 175 except KeyError:
176 176 return False
177 177
178 178 def headrevs(self):
179 179 if self.filteredrevs:
180 180 try:
181 181 return self.index.headrevsfiltered(self.filteredrevs)
182 182 # AttributeError covers non-c-extension environments and
183 183 # old c extensions without filter handling.
184 184 except AttributeError:
185 185 return self._headrevs()
186 186
187 187 return super(changelog, self).headrevs()
188 188
189 189 def strip(self, *args, **kwargs):
190 190 # XXX make something better than assert
191 191 # We can't expect proper strip behavior if we are filtered.
192 192 assert not self.filteredrevs
193 193 super(changelog, self).strip(*args, **kwargs)
194 194
195 195 def rev(self, node):
196 196 """filtered version of revlog.rev"""
197 197 r = super(changelog, self).rev(node)
198 198 if r in self.filteredrevs:
199 199 raise error.FilteredLookupError(hex(node), self.indexfile,
200 200 _('filtered node'))
201 201 return r
202 202
203 203 def node(self, rev):
204 204 """filtered version of revlog.node"""
205 205 if rev in self.filteredrevs:
206 206 raise error.FilteredIndexError(rev)
207 207 return super(changelog, self).node(rev)
208 208
209 209 def linkrev(self, rev):
210 210 """filtered version of revlog.linkrev"""
211 211 if rev in self.filteredrevs:
212 212 raise error.FilteredIndexError(rev)
213 213 return super(changelog, self).linkrev(rev)
214 214
215 215 def parentrevs(self, rev):
216 216 """filtered version of revlog.parentrevs"""
217 217 if rev in self.filteredrevs:
218 218 raise error.FilteredIndexError(rev)
219 219 return super(changelog, self).parentrevs(rev)
220 220
221 221 def flags(self, rev):
222 222 """filtered version of revlog.flags"""
223 223 if rev in self.filteredrevs:
224 224 raise error.FilteredIndexError(rev)
225 225 return super(changelog, self).flags(rev)
226 226
227 227 def delayupdate(self, tr):
228 228 "delay visibility of index updates to other readers"
229 229
230 230 if not self._delayed:
231 231 if len(self) == 0:
232 232 self._divert = True
233 233 if self._realopener.exists(self.indexfile + '.a'):
234 234 self._realopener.unlink(self.indexfile + '.a')
235 235 self.opener = _divertopener(self._realopener, self.indexfile)
236 236 else:
237 237 self._delaybuf = []
238 238 self.opener = _delayopener(self._realopener, self.indexfile,
239 239 self._delaybuf)
240 240 self._delayed = True
241 241 tr.addpending('cl-%i' % id(self), self._writepending)
242 242 tr.addfinalize('cl-%i' % id(self), self._finalize)
243 243
244 244 def _finalize(self, tr):
245 245 "finalize index updates"
246 246 self._delayed = False
247 247 self.opener = self._realopener
248 248 # move redirected index data back into place
249 249 if self._divert:
250 250 assert not self._delaybuf
251 251 tmpname = self.indexfile + ".a"
252 252 nfile = self.opener.open(tmpname)
253 253 nfile.close()
254 254 self.opener.rename(tmpname, self.indexfile)
255 255 elif self._delaybuf:
256 256 fp = self.opener(self.indexfile, 'a')
257 257 fp.write("".join(self._delaybuf))
258 258 fp.close()
259 259 self._delaybuf = None
260 260 self._divert = False
261 261 # split when we're done
262 262 self.checkinlinesize(tr)
263 263
264 264 def readpending(self, file):
265 265 r = revlog.revlog(self.opener, file)
266 266 self.index = r.index
267 267 self.nodemap = r.nodemap
268 268 self._nodecache = r._nodecache
269 269 self._chunkcache = r._chunkcache
270 270
271 271 def _writepending(self, tr):
272 272 "create a file containing the unfinalized state for pretxnchangegroup"
273 273 if self._delaybuf:
274 274 # make a temporary copy of the index
275 275 fp1 = self._realopener(self.indexfile)
276 fp2 = self._realopener(self.indexfile + ".a", "w")
276 pendingfilename = self.indexfile + ".a"
277 # register as a temp file to ensure cleanup on failure
278 tr.registertmp(pendingfilename)
279 # write existing data
280 fp2 = self._realopener(pendingfilename, "w")
277 281 fp2.write(fp1.read())
278 282 # add pending data
279 283 fp2.write("".join(self._delaybuf))
280 284 fp2.close()
281 285 # switch modes so finalize can simply rename
282 286 self._delaybuf = None
283 287 self._divert = True
284 288 self.opener = _divertopener(self._realopener, self.indexfile)
285 289
286 290 if self._divert:
287 291 return True
288 292
289 293 return False
290 294
291 295 def checkinlinesize(self, tr, fp=None):
292 296 if not self._delayed:
293 297 revlog.revlog.checkinlinesize(self, tr, fp)
294 298
295 299 def read(self, node):
296 300 """
297 301 format used:
298 302 nodeid\n : manifest node in ascii
299 303 user\n : user, no \n or \r allowed
300 304 time tz extra\n : date (time is int or float, timezone is int)
301 305 : extra is metadata, encoded and separated by '\0'
302 306 : older versions ignore it
303 307 files\n\n : files modified by the cset, no \n or \r allowed
304 308 (.*) : comment (free text, ideally utf-8)
305 309
306 310 changelog v0 doesn't use extra
307 311 """
308 312 text = self.revision(node)
309 313 if not text:
310 314 return (nullid, "", (0, 0), [], "", _defaultextra)
311 315 last = text.index("\n\n")
312 316 desc = encoding.tolocal(text[last + 2:])
313 317 l = text[:last].split('\n')
314 318 manifest = bin(l[0])
315 319 user = encoding.tolocal(l[1])
316 320
317 321 tdata = l[2].split(' ', 2)
318 322 if len(tdata) != 3:
319 323 time = float(tdata[0])
320 324 try:
321 325 # various tools did silly things with the time zone field.
322 326 timezone = int(tdata[1])
323 327 except ValueError:
324 328 timezone = 0
325 329 extra = _defaultextra
326 330 else:
327 331 time, timezone = float(tdata[0]), int(tdata[1])
328 332 extra = decodeextra(tdata[2])
329 333
330 334 files = l[3:]
331 335 return (manifest, user, (time, timezone), files, desc, extra)
332 336
333 337 def add(self, manifest, files, desc, transaction, p1, p2,
334 338 user, date=None, extra=None):
335 339 # Convert to UTF-8 encoded bytestrings as the very first
336 340 # thing: calling any method on a localstr object will turn it
337 341 # into a str object and the cached UTF-8 string is thus lost.
338 342 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
339 343
340 344 user = user.strip()
341 345 # An empty username or a username with a "\n" will make the
342 346 # revision text contain two "\n\n" sequences -> corrupt
343 347 # repository since read cannot unpack the revision.
344 348 if not user:
345 349 raise error.RevlogError(_("empty username"))
346 350 if "\n" in user:
347 351 raise error.RevlogError(_("username %s contains a newline")
348 352 % repr(user))
349 353
350 354 desc = stripdesc(desc)
351 355
352 356 if date:
353 357 parseddate = "%d %d" % util.parsedate(date)
354 358 else:
355 359 parseddate = "%d %d" % util.makedate()
356 360 if extra:
357 361 branch = extra.get("branch")
358 362 if branch in ("default", ""):
359 363 del extra["branch"]
360 364 elif branch in (".", "null", "tip"):
361 365 raise error.RevlogError(_('the name \'%s\' is reserved')
362 366 % branch)
363 367 if extra:
364 368 extra = encodeextra(extra)
365 369 parseddate = "%s %s" % (parseddate, extra)
366 370 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
367 371 text = "\n".join(l)
368 372 return self.addrevision(text, transaction, len(self), p1, p2)
369 373
370 374 def branchinfo(self, rev):
371 375 """return the branch name and open/close state of a revision
372 376
373 377 This function exists because creating a changectx object
374 378 just to access this is costly."""
375 379 extra = self.read(rev)[5]
376 380 return encoding.tolocal(extra.get("branch")), 'close' in extra
@@ -1,643 +1,656 b''
1 1 commit hooks can see env vars
2 2
3 3 $ hg init a
4 4 $ cd a
5 5 $ cat > .hg/hgrc <<EOF
6 6 > [hooks]
7 7 > commit = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" commit"
8 8 > commit.b = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" commit.b"
9 9 > precommit = sh -c "HG_LOCAL= HG_NODE= HG_TAG= python \"$TESTDIR/printenv.py\" precommit"
10 10 > pretxncommit = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxncommit"
11 11 > pretxncommit.tip = hg -q tip
12 12 > pre-identify = python "$TESTDIR/printenv.py" pre-identify 1
13 13 > pre-cat = python "$TESTDIR/printenv.py" pre-cat
14 14 > post-cat = python "$TESTDIR/printenv.py" post-cat
15 15 > EOF
16 16 $ echo a > a
17 17 $ hg add a
18 18 $ hg commit -m a
19 19 precommit hook: HG_PARENT1=0000000000000000000000000000000000000000
20 20 pretxncommit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000 HG_PENDING=$TESTTMP/a
21 21 0:cb9a9f314b8b
22 22 commit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
23 23 commit.b hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
24 24
25 25 $ hg clone . ../b
26 26 updating to branch default
27 27 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
28 28 $ cd ../b
29 29
30 30 changegroup hooks can see env vars
31 31
32 32 $ cat > .hg/hgrc <<EOF
33 33 > [hooks]
34 34 > prechangegroup = python "$TESTDIR/printenv.py" prechangegroup
35 35 > changegroup = python "$TESTDIR/printenv.py" changegroup
36 36 > incoming = python "$TESTDIR/printenv.py" incoming
37 37 > EOF
38 38
39 39 pretxncommit and commit hooks can see both parents of merge
40 40
41 41 $ cd ../a
42 42 $ echo b >> a
43 43 $ hg commit -m a1 -d "1 0"
44 44 precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
45 45 pretxncommit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
46 46 1:ab228980c14d
47 47 commit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
48 48 commit.b hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
49 49 $ hg update -C 0
50 50 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
51 51 $ echo b > b
52 52 $ hg add b
53 53 $ hg commit -m b -d '1 0'
54 54 precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
55 55 pretxncommit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
56 56 2:ee9deb46ab31
57 57 commit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
58 58 commit.b hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
59 59 created new head
60 60 $ hg merge 1
61 61 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
62 62 (branch merge, don't forget to commit)
63 63 $ hg commit -m merge -d '2 0'
64 64 precommit hook: HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
65 65 pretxncommit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd HG_PENDING=$TESTTMP/a
66 66 3:07f3376c1e65
67 67 commit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
68 68 commit.b hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
69 69
70 70 test generic hooks
71 71
72 72 $ hg id
73 73 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=[]
74 74 abort: pre-identify hook exited with status 1
75 75 [255]
76 76 $ hg cat b
77 77 pre-cat hook: HG_ARGS=cat b HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b']
78 78 b
79 79 post-cat hook: HG_ARGS=cat b HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b'] HG_RESULT=0
80 80
81 81 $ cd ../b
82 82 $ hg pull ../a
83 83 pulling from ../a
84 84 searching for changes
85 85 prechangegroup hook: HG_SOURCE=pull HG_URL=file:$TESTTMP/a
86 86 adding changesets
87 87 adding manifests
88 88 adding file changes
89 89 added 3 changesets with 2 changes to 2 files
90 90 changegroup hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_URL=file:$TESTTMP/a
91 91 incoming hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_URL=file:$TESTTMP/a
92 92 incoming hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_SOURCE=pull HG_URL=file:$TESTTMP/a
93 93 incoming hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_URL=file:$TESTTMP/a
94 94 (run 'hg update' to get a working copy)
95 95
96 96 tag hooks can see env vars
97 97
98 98 $ cd ../a
99 99 $ cat >> .hg/hgrc <<EOF
100 100 > pretag = python "$TESTDIR/printenv.py" pretag
101 101 > tag = sh -c "HG_PARENT1= HG_PARENT2= python \"$TESTDIR/printenv.py\" tag"
102 102 > EOF
103 103 $ hg tag -d '3 0' a
104 104 pretag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
105 105 precommit hook: HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
106 106 pretxncommit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PENDING=$TESTTMP/a
107 107 4:539e4b31b6dc
108 108 tag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
109 109 commit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
110 110 commit.b hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
111 111 $ hg tag -l la
112 112 pretag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
113 113 tag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
114 114
115 115 pretag hook can forbid tagging
116 116
117 117 $ echo "pretag.forbid = python \"$TESTDIR/printenv.py\" pretag.forbid 1" >> .hg/hgrc
118 118 $ hg tag -d '4 0' fa
119 119 pretag hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
120 120 pretag.forbid hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
121 121 abort: pretag.forbid hook exited with status 1
122 122 [255]
123 123 $ hg tag -l fla
124 124 pretag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
125 125 pretag.forbid hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
126 126 abort: pretag.forbid hook exited with status 1
127 127 [255]
128 128
129 129 pretxncommit hook can see changeset, can roll back txn, changeset no
130 130 more there after
131 131
132 132 $ echo "pretxncommit.forbid0 = hg tip -q" >> .hg/hgrc
133 133 $ echo "pretxncommit.forbid1 = python \"$TESTDIR/printenv.py\" pretxncommit.forbid 1" >> .hg/hgrc
134 134 $ echo z > z
135 135 $ hg add z
136 136 $ hg -q tip
137 137 4:539e4b31b6dc
138 138 $ hg commit -m 'fail' -d '4 0'
139 139 precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
140 140 pretxncommit hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
141 141 5:6f611f8018c1
142 142 5:6f611f8018c1
143 143 pretxncommit.forbid hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
144 144 transaction abort!
145 145 rollback completed
146 146 abort: pretxncommit.forbid1 hook exited with status 1
147 147 [255]
148 148 $ hg -q tip
149 149 4:539e4b31b6dc
150 150
151 (Check that no 'changelog.i.a' file were left behind)
152
153 $ ls -1 .hg/store/
154 00changelog.i
155 00manifest.i
156 data
157 fncache
158 journal.phaseroots
159 phaseroots
160 undo
161 undo.phaseroots
162
163
151 164 precommit hook can prevent commit
152 165
153 166 $ echo "precommit.forbid = python \"$TESTDIR/printenv.py\" precommit.forbid 1" >> .hg/hgrc
154 167 $ hg commit -m 'fail' -d '4 0'
155 168 precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
156 169 precommit.forbid hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
157 170 abort: precommit.forbid hook exited with status 1
158 171 [255]
159 172 $ hg -q tip
160 173 4:539e4b31b6dc
161 174
162 175 preupdate hook can prevent update
163 176
164 177 $ echo "preupdate = python \"$TESTDIR/printenv.py\" preupdate" >> .hg/hgrc
165 178 $ hg update 1
166 179 preupdate hook: HG_PARENT1=ab228980c14d
167 180 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
168 181
169 182 update hook
170 183
171 184 $ echo "update = python \"$TESTDIR/printenv.py\" update" >> .hg/hgrc
172 185 $ hg update
173 186 preupdate hook: HG_PARENT1=539e4b31b6dc
174 187 update hook: HG_ERROR=0 HG_PARENT1=539e4b31b6dc
175 188 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
176 189
177 190 pushkey hook
178 191
179 192 $ echo "pushkey = python \"$TESTDIR/printenv.py\" pushkey" >> .hg/hgrc
180 193 $ cd ../b
181 194 $ hg bookmark -r null foo
182 195 $ hg push -B foo ../a
183 196 pushing to ../a
184 197 searching for changes
185 198 no changes found
186 199 pushkey hook: HG_KEY=foo HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_RET=1
187 200 exporting bookmark foo
188 201 [1]
189 202 $ cd ../a
190 203
191 204 listkeys hook
192 205
193 206 $ echo "listkeys = python \"$TESTDIR/printenv.py\" listkeys" >> .hg/hgrc
194 207 $ hg bookmark -r null bar
195 208 $ cd ../b
196 209 $ hg pull -B bar ../a
197 210 pulling from ../a
198 211 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
199 212 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
200 213 no changes found
201 214 listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
202 215 adding remote bookmark bar
203 216 $ cd ../a
204 217
205 218 test that prepushkey can prevent incoming keys
206 219
207 220 $ echo "prepushkey = python \"$TESTDIR/printenv.py\" prepushkey.forbid 1" >> .hg/hgrc
208 221 $ cd ../b
209 222 $ hg bookmark -r null baz
210 223 $ hg push -B baz ../a
211 224 pushing to ../a
212 225 searching for changes
213 226 listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
214 227 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
215 228 no changes found
216 229 listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
217 230 prepushkey.forbid hook: HG_KEY=baz HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000
218 231 abort: prepushkey hook exited with status 1
219 232 [255]
220 233 $ cd ../a
221 234
222 235 test that prelistkeys can prevent listing keys
223 236
224 237 $ echo "prelistkeys = python \"$TESTDIR/printenv.py\" prelistkeys.forbid 1" >> .hg/hgrc
225 238 $ hg bookmark -r null quux
226 239 $ cd ../b
227 240 $ hg pull -B quux ../a
228 241 pulling from ../a
229 242 prelistkeys.forbid hook: HG_NAMESPACE=bookmarks
230 243 abort: prelistkeys hook exited with status 1
231 244 [255]
232 245 $ cd ../a
233 246 $ rm .hg/hgrc
234 247
235 248 prechangegroup hook can prevent incoming changes
236 249
237 250 $ cd ../b
238 251 $ hg -q tip
239 252 3:07f3376c1e65
240 253 $ cat > .hg/hgrc <<EOF
241 254 > [hooks]
242 255 > prechangegroup.forbid = python "$TESTDIR/printenv.py" prechangegroup.forbid 1
243 256 > EOF
244 257 $ hg pull ../a
245 258 pulling from ../a
246 259 searching for changes
247 260 prechangegroup.forbid hook: HG_SOURCE=pull HG_URL=file:$TESTTMP/a
248 261 abort: prechangegroup.forbid hook exited with status 1
249 262 [255]
250 263
251 264 pretxnchangegroup hook can see incoming changes, can roll back txn,
252 265 incoming changes no longer there after
253 266
254 267 $ cat > .hg/hgrc <<EOF
255 268 > [hooks]
256 269 > pretxnchangegroup.forbid0 = hg tip -q
257 270 > pretxnchangegroup.forbid1 = python "$TESTDIR/printenv.py" pretxnchangegroup.forbid 1
258 271 > EOF
259 272 $ hg pull ../a
260 273 pulling from ../a
261 274 searching for changes
262 275 adding changesets
263 276 adding manifests
264 277 adding file changes
265 278 added 1 changesets with 1 changes to 1 files
266 279 4:539e4b31b6dc
267 280 pretxnchangegroup.forbid hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/b HG_SOURCE=pull HG_URL=file:$TESTTMP/a
268 281 transaction abort!
269 282 rollback completed
270 283 abort: pretxnchangegroup.forbid1 hook exited with status 1
271 284 [255]
272 285 $ hg -q tip
273 286 3:07f3376c1e65
274 287
275 288 outgoing hooks can see env vars
276 289
277 290 $ rm .hg/hgrc
278 291 $ cat > ../a/.hg/hgrc <<EOF
279 292 > [hooks]
280 293 > preoutgoing = python "$TESTDIR/printenv.py" preoutgoing
281 294 > outgoing = python "$TESTDIR/printenv.py" outgoing
282 295 > EOF
283 296 $ hg pull ../a
284 297 pulling from ../a
285 298 searching for changes
286 299 preoutgoing hook: HG_SOURCE=pull
287 300 adding changesets
288 301 outgoing hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_SOURCE=pull
289 302 adding manifests
290 303 adding file changes
291 304 added 1 changesets with 1 changes to 1 files
292 305 adding remote bookmark quux
293 306 (run 'hg update' to get a working copy)
294 307 $ hg rollback
295 308 repository tip rolled back to revision 3 (undo pull)
296 309
297 310 preoutgoing hook can prevent outgoing changes
298 311
299 312 $ echo "preoutgoing.forbid = python \"$TESTDIR/printenv.py\" preoutgoing.forbid 1" >> ../a/.hg/hgrc
300 313 $ hg pull ../a
301 314 pulling from ../a
302 315 searching for changes
303 316 preoutgoing hook: HG_SOURCE=pull
304 317 preoutgoing.forbid hook: HG_SOURCE=pull
305 318 abort: preoutgoing.forbid hook exited with status 1
306 319 [255]
307 320
308 321 outgoing hooks work for local clones
309 322
310 323 $ cd ..
311 324 $ cat > a/.hg/hgrc <<EOF
312 325 > [hooks]
313 326 > preoutgoing = python "$TESTDIR/printenv.py" preoutgoing
314 327 > outgoing = python "$TESTDIR/printenv.py" outgoing
315 328 > EOF
316 329 $ hg clone a c
317 330 preoutgoing hook: HG_SOURCE=clone
318 331 outgoing hook: HG_NODE=0000000000000000000000000000000000000000 HG_SOURCE=clone
319 332 updating to branch default
320 333 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
321 334 $ rm -rf c
322 335
323 336 preoutgoing hook can prevent outgoing changes for local clones
324 337
325 338 $ echo "preoutgoing.forbid = python \"$TESTDIR/printenv.py\" preoutgoing.forbid 1" >> a/.hg/hgrc
326 339 $ hg clone a zzz
327 340 preoutgoing hook: HG_SOURCE=clone
328 341 preoutgoing.forbid hook: HG_SOURCE=clone
329 342 abort: preoutgoing.forbid hook exited with status 1
330 343 [255]
331 344
332 345 $ cd "$TESTTMP/b"
333 346
334 347 $ cat > hooktests.py <<EOF
335 348 > from mercurial import util
336 349 >
337 350 > uncallable = 0
338 351 >
339 352 > def printargs(args):
340 353 > args.pop('ui', None)
341 354 > args.pop('repo', None)
342 355 > a = list(args.items())
343 356 > a.sort()
344 357 > print 'hook args:'
345 358 > for k, v in a:
346 359 > print ' ', k, v
347 360 >
348 361 > def passhook(**args):
349 362 > printargs(args)
350 363 >
351 364 > def failhook(**args):
352 365 > printargs(args)
353 366 > return True
354 367 >
355 368 > class LocalException(Exception):
356 369 > pass
357 370 >
358 371 > def raisehook(**args):
359 372 > raise LocalException('exception from hook')
360 373 >
361 374 > def aborthook(**args):
362 375 > raise util.Abort('raise abort from hook')
363 376 >
364 377 > def brokenhook(**args):
365 378 > return 1 + {}
366 379 >
367 380 > def verbosehook(ui, **args):
368 381 > ui.note('verbose output from hook\n')
369 382 >
370 383 > def printtags(ui, repo, **args):
371 384 > print sorted(repo.tags())
372 385 >
373 386 > class container:
374 387 > unreachable = 1
375 388 > EOF
376 389
377 390 test python hooks
378 391
379 392 #if windows
380 393 $ PYTHONPATH="$TESTTMP/b;$PYTHONPATH"
381 394 #else
382 395 $ PYTHONPATH="$TESTTMP/b:$PYTHONPATH"
383 396 #endif
384 397 $ export PYTHONPATH
385 398
386 399 $ echo '[hooks]' > ../a/.hg/hgrc
387 400 $ echo 'preoutgoing.broken = python:hooktests.brokenhook' >> ../a/.hg/hgrc
388 401 $ hg pull ../a 2>&1 | grep 'raised an exception'
389 402 error: preoutgoing.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
390 403
391 404 $ echo '[hooks]' > ../a/.hg/hgrc
392 405 $ echo 'preoutgoing.raise = python:hooktests.raisehook' >> ../a/.hg/hgrc
393 406 $ hg pull ../a 2>&1 | grep 'raised an exception'
394 407 error: preoutgoing.raise hook raised an exception: exception from hook
395 408
396 409 $ echo '[hooks]' > ../a/.hg/hgrc
397 410 $ echo 'preoutgoing.abort = python:hooktests.aborthook' >> ../a/.hg/hgrc
398 411 $ hg pull ../a
399 412 pulling from ../a
400 413 searching for changes
401 414 error: preoutgoing.abort hook failed: raise abort from hook
402 415 abort: raise abort from hook
403 416 [255]
404 417
405 418 $ echo '[hooks]' > ../a/.hg/hgrc
406 419 $ echo 'preoutgoing.fail = python:hooktests.failhook' >> ../a/.hg/hgrc
407 420 $ hg pull ../a
408 421 pulling from ../a
409 422 searching for changes
410 423 hook args:
411 424 hooktype preoutgoing
412 425 source pull
413 426 abort: preoutgoing.fail hook failed
414 427 [255]
415 428
416 429 $ echo '[hooks]' > ../a/.hg/hgrc
417 430 $ echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc
418 431 $ hg pull ../a
419 432 pulling from ../a
420 433 searching for changes
421 434 abort: preoutgoing.uncallable hook is invalid ("hooktests.uncallable" is not callable)
422 435 [255]
423 436
424 437 $ echo '[hooks]' > ../a/.hg/hgrc
425 438 $ echo 'preoutgoing.nohook = python:hooktests.nohook' >> ../a/.hg/hgrc
426 439 $ hg pull ../a
427 440 pulling from ../a
428 441 searching for changes
429 442 abort: preoutgoing.nohook hook is invalid ("hooktests.nohook" is not defined)
430 443 [255]
431 444
432 445 $ echo '[hooks]' > ../a/.hg/hgrc
433 446 $ echo 'preoutgoing.nomodule = python:nomodule' >> ../a/.hg/hgrc
434 447 $ hg pull ../a
435 448 pulling from ../a
436 449 searching for changes
437 450 abort: preoutgoing.nomodule hook is invalid ("nomodule" not in a module)
438 451 [255]
439 452
440 453 $ echo '[hooks]' > ../a/.hg/hgrc
441 454 $ echo 'preoutgoing.badmodule = python:nomodule.nowhere' >> ../a/.hg/hgrc
442 455 $ hg pull ../a
443 456 pulling from ../a
444 457 searching for changes
445 458 abort: preoutgoing.badmodule hook is invalid (import of "nomodule" failed)
446 459 [255]
447 460
448 461 $ echo '[hooks]' > ../a/.hg/hgrc
449 462 $ echo 'preoutgoing.unreachable = python:hooktests.container.unreachable' >> ../a/.hg/hgrc
450 463 $ hg pull ../a
451 464 pulling from ../a
452 465 searching for changes
453 466 abort: preoutgoing.unreachable hook is invalid (import of "hooktests.container" failed)
454 467 [255]
455 468
456 469 $ echo '[hooks]' > ../a/.hg/hgrc
457 470 $ echo 'preoutgoing.pass = python:hooktests.passhook' >> ../a/.hg/hgrc
458 471 $ hg pull ../a
459 472 pulling from ../a
460 473 searching for changes
461 474 hook args:
462 475 hooktype preoutgoing
463 476 source pull
464 477 adding changesets
465 478 adding manifests
466 479 adding file changes
467 480 added 1 changesets with 1 changes to 1 files
468 481 adding remote bookmark quux
469 482 (run 'hg update' to get a working copy)
470 483
471 484 make sure --traceback works
472 485
473 486 $ echo '[hooks]' > .hg/hgrc
474 487 $ echo 'commit.abort = python:hooktests.aborthook' >> .hg/hgrc
475 488
476 489 $ echo aa > a
477 490 $ hg --traceback commit -d '0 0' -ma 2>&1 | grep '^Traceback'
478 491 Traceback (most recent call last):
479 492
480 493 $ cd ..
481 494 $ hg init c
482 495 $ cd c
483 496
484 497 $ cat > hookext.py <<EOF
485 498 > def autohook(**args):
486 499 > print "Automatically installed hook"
487 500 >
488 501 > def reposetup(ui, repo):
489 502 > repo.ui.setconfig("hooks", "commit.auto", autohook)
490 503 > EOF
491 504 $ echo '[extensions]' >> .hg/hgrc
492 505 $ echo 'hookext = hookext.py' >> .hg/hgrc
493 506
494 507 $ touch foo
495 508 $ hg add foo
496 509 $ hg ci -d '0 0' -m 'add foo'
497 510 Automatically installed hook
498 511 $ echo >> foo
499 512 $ hg ci --debug -d '0 0' -m 'change foo'
500 513 foo
501 514 calling hook commit.auto: hgext_hookext.autohook
502 515 Automatically installed hook
503 516 committed changeset 1:52998019f6252a2b893452765fcb0a47351a5708
504 517
505 518 $ hg showconfig hooks
506 519 hooks.commit.auto=<function autohook at *> (glob)
507 520
508 521 test python hook configured with python:[file]:[hook] syntax
509 522
510 523 $ cd ..
511 524 $ mkdir d
512 525 $ cd d
513 526 $ hg init repo
514 527 $ mkdir hooks
515 528
516 529 $ cd hooks
517 530 $ cat > testhooks.py <<EOF
518 531 > def testhook(**args):
519 532 > print 'hook works'
520 533 > EOF
521 534 $ echo '[hooks]' > ../repo/.hg/hgrc
522 535 $ echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc
523 536
524 537 $ cd ../repo
525 538 $ hg commit -d '0 0'
526 539 hook works
527 540 nothing changed
528 541 [1]
529 542
530 543 $ echo '[hooks]' > .hg/hgrc
531 544 $ echo "update.ne = python:`pwd`/nonexistent.py:testhook" >> .hg/hgrc
532 545 $ echo "pre-identify.npmd = python:`pwd`/:no_python_module_dir" >> .hg/hgrc
533 546
534 547 $ hg up null
535 548 loading update.ne hook failed:
536 549 abort: No such file or directory: $TESTTMP/d/repo/nonexistent.py
537 550 [255]
538 551
539 552 $ hg id
540 553 loading pre-identify.npmd hook failed:
541 554 abort: No module named repo!
542 555 [255]
543 556
544 557 $ cd ../../b
545 558
546 559 make sure --traceback works on hook import failure
547 560
548 561 $ cat > importfail.py <<EOF
549 562 > import somebogusmodule
550 563 > # dereference something in the module to force demandimport to load it
551 564 > somebogusmodule.whatever
552 565 > EOF
553 566
554 567 $ echo '[hooks]' > .hg/hgrc
555 568 $ echo 'precommit.importfail = python:importfail.whatever' >> .hg/hgrc
556 569
557 570 $ echo a >> a
558 571 $ hg --traceback commit -ma 2>&1 | egrep -v '^( +File| [a-zA-Z(])'
559 572 exception from first failed import attempt:
560 573 Traceback (most recent call last):
561 574 ImportError: No module named somebogusmodule
562 575 exception from second failed import attempt:
563 576 Traceback (most recent call last):
564 577 ImportError: No module named hgext_importfail
565 578 Traceback (most recent call last):
566 579 Abort: precommit.importfail hook is invalid (import of "importfail" failed)
567 580 abort: precommit.importfail hook is invalid (import of "importfail" failed)
568 581
569 582 Issue1827: Hooks Update & Commit not completely post operation
570 583
571 584 commit and update hooks should run after command completion
572 585
573 586 $ echo '[hooks]' > .hg/hgrc
574 587 $ echo 'commit = hg id' >> .hg/hgrc
575 588 $ echo 'update = hg id' >> .hg/hgrc
576 589 $ echo bb > a
577 590 $ hg ci -ma
578 591 223eafe2750c tip
579 592 $ hg up 0
580 593 cb9a9f314b8b
581 594 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
582 595
583 596 make sure --verbose (and --quiet/--debug etc.) are propagated to the local ui
584 597 that is passed to pre/post hooks
585 598
586 599 $ echo '[hooks]' > .hg/hgrc
587 600 $ echo 'pre-identify = python:hooktests.verbosehook' >> .hg/hgrc
588 601 $ hg id
589 602 cb9a9f314b8b
590 603 $ hg id --verbose
591 604 calling hook pre-identify: hooktests.verbosehook
592 605 verbose output from hook
593 606 cb9a9f314b8b
594 607
595 608 Ensure hooks can be prioritized
596 609
597 610 $ echo '[hooks]' > .hg/hgrc
598 611 $ echo 'pre-identify.a = python:hooktests.verbosehook' >> .hg/hgrc
599 612 $ echo 'pre-identify.b = python:hooktests.verbosehook' >> .hg/hgrc
600 613 $ echo 'priority.pre-identify.b = 1' >> .hg/hgrc
601 614 $ echo 'pre-identify.c = python:hooktests.verbosehook' >> .hg/hgrc
602 615 $ hg id --verbose
603 616 calling hook pre-identify.b: hooktests.verbosehook
604 617 verbose output from hook
605 618 calling hook pre-identify.a: hooktests.verbosehook
606 619 verbose output from hook
607 620 calling hook pre-identify.c: hooktests.verbosehook
608 621 verbose output from hook
609 622 cb9a9f314b8b
610 623
611 624 new tags must be visible in pretxncommit (issue3210)
612 625
613 626 $ echo 'pretxncommit.printtags = python:hooktests.printtags' >> .hg/hgrc
614 627 $ hg tag -f foo
615 628 ['a', 'foo', 'tip']
616 629
617 630 new commits must be visible in pretxnchangegroup (issue3428)
618 631
619 632 $ cd ..
620 633 $ hg init to
621 634 $ echo '[hooks]' >> to/.hg/hgrc
622 635 $ echo 'pretxnchangegroup = hg --traceback tip' >> to/.hg/hgrc
623 636 $ echo a >> to/a
624 637 $ hg --cwd to ci -Ama
625 638 adding a
626 639 $ hg clone to from
627 640 updating to branch default
628 641 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
629 642 $ echo aa >> from/a
630 643 $ hg --cwd from ci -mb
631 644 $ hg --cwd from push
632 645 pushing to $TESTTMP/to (glob)
633 646 searching for changes
634 647 adding changesets
635 648 adding manifests
636 649 adding file changes
637 650 added 1 changesets with 1 changes to 1 files
638 651 changeset: 1:9836a07b9b9d
639 652 tag: tip
640 653 user: test
641 654 date: Thu Jan 01 00:00:00 1970 +0000
642 655 summary: b
643 656
General Comments 0
You need to be logged in to leave comments. Login now