##// END OF EJS Templates
persistent-nodemap: introduce a test to highlight possible race...
marmoute -
r48852:52018f8e stable
parent child Browse files
Show More
@@ -0,0 +1,294 b''
1 """Create the race condition for issue6554
2
3 The persistent nodemap issues had an issue where a second writer could
4 overwrite the data that a previous write just wrote. The would break the append
5 only garantee of the persistent nodemap and could confuse reader. This
6 extensions create all the necessary synchronisation point to the race condition
7 to happen.
8
9 It involves 3 process <LEFT> (a writer) <RIGHT> (a writer) and <READER>
10
11 [1] <LEFT> take the lock and start a transaction
12 [2] <LEFT> updated `00changelog.i` with the new data
13 [3] <RIGHT> reads:
14 - the new changelog index `00changelog.i`
15 - the old `00changelog.n`
16 [4] <LEFT> update the persistent nodemap:
17 - writing new data from the last valid offset
18 - updating the docket (00changelog.n)
19 [5] <LEFT> release the lock
20 [6] <RIGHT> grab the lock and run `repo.invalidate`
21 [7] <READER> reads:
22 - the changelog index after <LEFT> write
23 - the nodemap docket after <LEFT> write
24 [8] <RIGHT> reload the changelog since `00changelog.n` changed
25 /!\ This is the faulty part in issue 6554, the outdated docket is kept
26 [9] <RIGHT> write:
27 - the changelog index (00changelog.i)
28 - the nodemap data (00changelog*.nd)
29 /!\ if the outdated docket is used, the write starts from the same ofset
30 /!\ as in [4], overwriting data that <LEFT> wrote in step [4].
31 - the nodemap docket (00changelog.n)
32 [10] <READER> reads the nodemap data from `00changelog*.nd`
33 /!\ if step [9] was wrong, the data matching the docket that <READER>
34 /!\ loaded have been overwritten and the expected root-nodes is no longer
35 /!\ valid.
36 """
37
38 from __future__ import print_function
39
40 import os
41
42 from mercurial.revlogutils.constants import KIND_CHANGELOG
43
44 from mercurial import (
45 changelog,
46 encoding,
47 extensions,
48 localrepo,
49 node,
50 pycompat,
51 registrar,
52 testing,
53 util,
54 )
55
56 from mercurial.revlogutils import (
57 nodemap as nodemaputil,
58 )
59
60 configtable = {}
61 configitem = registrar.configitem(configtable)
62
63 configitem(b'devel', b'nodemap-race.role', default=None)
64
65 cmdtable = {}
66 command = registrar.command(cmdtable)
67
68 LEFT = b'left'
69 RIGHT = b'right'
70 READER = b'reader'
71
72 SYNC_DIR = os.path.join(encoding.environ[b'TESTTMP'], b'sync-files')
73
74 # mark the end of step [1]
75 FILE_LEFT_LOCKED = os.path.join(SYNC_DIR, b'left-locked')
76 # mark that step [3] is ready to run.
77 FILE_RIGHT_READY_TO_LOCK = os.path.join(SYNC_DIR, b'right-ready-to-lock')
78
79 # mark the end of step [2]
80 FILE_LEFT_CL_DATA_WRITE = os.path.join(SYNC_DIR, b'left-data')
81 # mark the end of step [4]
82 FILE_LEFT_CL_NODEMAP_WRITE = os.path.join(SYNC_DIR, b'left-nodemap')
83 # mark the end of step [3]
84 FILE_RIGHT_CL_NODEMAP_READ = os.path.join(SYNC_DIR, b'right-nodemap')
85 # mark that step [9] is read to run
86 FILE_RIGHT_CL_NODEMAP_PRE_WRITE = os.path.join(
87 SYNC_DIR, b'right-pre-nodemap-write'
88 )
89 # mark that step [9] has run.
90 FILE_RIGHT_CL_NODEMAP_POST_WRITE = os.path.join(
91 SYNC_DIR, b'right-post-nodemap-write'
92 )
93 # mark that step [7] is ready to run
94 FILE_READER_READY = os.path.join(SYNC_DIR, b'reader-ready')
95 # mark that step [7] has run
96 FILE_READER_READ_DOCKET = os.path.join(SYNC_DIR, b'reader-read-docket')
97
98
99 def _print(*args, **kwargs):
100 print(*args, **kwargs)
101
102
103 def _role(repo):
104 """find the role associated with the process"""
105 return repo.ui.config(b'devel', b'nodemap-race.role')
106
107
108 def wrap_changelog_finalize(orig, cl, tr):
109 """wrap the update of `00changelog.i` during transaction finalization
110
111 This is useful for synchronisation before or after the file is updated on disk.
112 """
113 role = getattr(tr, '_race_role', None)
114 if role == RIGHT:
115 print('right ready to write, waiting for reader')
116 testing.wait_file(FILE_READER_READY)
117 testing.write_file(FILE_RIGHT_CL_NODEMAP_PRE_WRITE)
118 testing.wait_file(FILE_READER_READ_DOCKET)
119 print('right proceeding with writing its changelog index and nodemap')
120 ret = orig(cl, tr)
121 print("finalized changelog write")
122 if role == LEFT:
123 testing.write_file(FILE_LEFT_CL_DATA_WRITE)
124 return ret
125
126
127 def wrap_persist_nodemap(orig, tr, revlog, *args, **kwargs):
128 """wrap the update of `00changelog.n` and `*.nd` during tr finalization
129
130 This is useful for synchronisation before or after the files are updated on
131 disk.
132 """
133 is_cl = revlog.target[0] == KIND_CHANGELOG
134 role = getattr(tr, '_race_role', None)
135 if is_cl:
136 if role == LEFT:
137 testing.wait_file(FILE_RIGHT_CL_NODEMAP_READ)
138 if is_cl:
139 print("persisting changelog nodemap")
140 print(" new data start at", revlog._nodemap_docket.data_length)
141 ret = orig(tr, revlog, *args, **kwargs)
142 if is_cl:
143 print("persisted changelog nodemap")
144 print_nodemap_details(revlog)
145 if role == LEFT:
146 testing.write_file(FILE_LEFT_CL_NODEMAP_WRITE)
147 elif role == RIGHT:
148 testing.write_file(FILE_RIGHT_CL_NODEMAP_POST_WRITE)
149 return ret
150
151
152 def print_nodemap_details(cl):
153 """print relevant information about the nodemap docket currently in memory"""
154 dkt = cl._nodemap_docket
155 print('docket-details:')
156 if dkt is None:
157 print(' <no-docket>')
158 return
159 print(' uid: ', pycompat.sysstr(dkt.uid))
160 print(' actual-tip: ', cl.tiprev())
161 print(' tip-rev: ', dkt.tip_rev)
162 print(' data-length:', dkt.data_length)
163
164
165 def wrap_persisted_data(orig, revlog):
166 """print some information about the nodemap information we just read
167
168 Used by the <READER> process only.
169 """
170 ret = orig(revlog)
171 if ret is not None:
172 docket, data = ret
173 file_path = nodemaputil._rawdata_filepath(revlog, docket)
174 file_path = revlog.opener.join(file_path)
175 file_size = os.path.getsize(file_path)
176 print('record-data-length:', docket.data_length)
177 print('actual-data-length:', len(data))
178 print('file-actual-length:', file_size)
179 return ret
180
181
182 def sync_read(orig):
183 """used by <READER> to force the race window
184
185 This make sure we read the docker from <LEFT> while reading the datafile
186 after <RIGHT> write.
187 """
188 orig()
189 testing.write_file(FILE_READER_READ_DOCKET)
190 print('reader: nodemap docket read')
191 testing.wait_file(FILE_RIGHT_CL_NODEMAP_POST_WRITE)
192
193
194 def uisetup(ui):
195 class RacedRepo(localrepo.localrepository):
196 def lock(self, wait=True):
197 # make sure <RIGHT> as the "Wrong" information in memory before
198 # grabbing the lock
199 newlock = self._currentlock(self._lockref) is None
200 if newlock and _role(self) == LEFT:
201 cl = self.unfiltered().changelog
202 print_nodemap_details(cl)
203 elif newlock and _role(self) == RIGHT:
204 testing.write_file(FILE_RIGHT_READY_TO_LOCK)
205 print('nodemap-race: right side start of the locking sequence')
206 testing.wait_file(FILE_LEFT_LOCKED)
207 testing.wait_file(FILE_LEFT_CL_DATA_WRITE)
208 self.invalidate(clearfilecache=True)
209 print('nodemap-race: right side reading changelog')
210 cl = self.unfiltered().changelog
211 tiprev = cl.tiprev()
212 tip = cl.node(tiprev)
213 tiprev2 = cl.rev(tip)
214 if tiprev != tiprev2:
215 raise RuntimeError(
216 'bad tip -round-trip %d %d' % (tiprev, tiprev2)
217 )
218 testing.write_file(FILE_RIGHT_CL_NODEMAP_READ)
219 print('nodemap-race: right side reading of changelog is done')
220 print_nodemap_details(cl)
221 testing.wait_file(FILE_LEFT_CL_NODEMAP_WRITE)
222 print('nodemap-race: right side ready to wait for the lock')
223 ret = super(RacedRepo, self).lock(wait=wait)
224 if newlock and _role(self) == LEFT:
225 print('nodemap-race: left side locked and ready to commit')
226 testing.write_file(FILE_LEFT_LOCKED)
227 testing.wait_file(FILE_RIGHT_READY_TO_LOCK)
228 cl = self.unfiltered().changelog
229 print_nodemap_details(cl)
230 elif newlock and _role(self) == RIGHT:
231 print('nodemap-race: right side locked and ready to commit')
232 cl = self.unfiltered().changelog
233 print_nodemap_details(cl)
234 return ret
235
236 def transaction(self, *args, **kwargs):
237 # duck punch the role on the transaction to help other pieces of code
238 tr = super(RacedRepo, self).transaction(*args, **kwargs)
239 tr._race_role = _role(self)
240 return tr
241
242 localrepo.localrepository = RacedRepo
243
244 extensions.wrapfunction(
245 nodemaputil, 'persist_nodemap', wrap_persist_nodemap
246 )
247 extensions.wrapfunction(
248 changelog.changelog, '_finalize', wrap_changelog_finalize
249 )
250
251
252 def reposetup(ui, repo):
253 if _role(repo) == READER:
254 extensions.wrapfunction(
255 nodemaputil, 'persisted_data', wrap_persisted_data
256 )
257 extensions.wrapfunction(nodemaputil, 'test_race_hook_1', sync_read)
258
259 class ReaderRepo(repo.__class__):
260 @util.propertycache
261 def changelog(self):
262 print('reader ready to read the changelog, waiting for right')
263 testing.write_file(FILE_READER_READY)
264 testing.wait_file(FILE_RIGHT_CL_NODEMAP_PRE_WRITE)
265 return super(ReaderRepo, self).changelog
266
267 repo.__class__ = ReaderRepo
268
269
270 @command(b'check-nodemap-race')
271 def cmd_check_nodemap_race(ui, repo):
272 """Run proper <READER> access in the race Windows and check nodemap content"""
273 repo = repo.unfiltered()
274 print('reader: reading changelog')
275 cl = repo.changelog
276 print('reader: changelog read')
277 print_nodemap_details(cl)
278 tip_rev = cl.tiprev()
279 tip_node = cl.node(tip_rev)
280 print('tip-rev: ', tip_rev)
281 print('tip-node:', node.short(tip_node).decode('ascii'))
282 print('node-rev:', cl.rev(tip_node))
283 for r in cl.revs():
284 n = cl.node(r)
285 try:
286 r2 = cl.rev(n)
287 except ValueError as exc:
288 print('error while checking revision:', r)
289 print(' ', exc)
290 return 1
291 else:
292 if r2 != r:
293 print('revision %d is missing from the nodemap' % r)
294 return 1
@@ -1,647 +1,657 b''
1 1 # nodemap.py - nodemap related code and utilities
2 2 #
3 3 # Copyright 2019 Pierre-Yves David <pierre-yves.david@octobus.net>
4 4 # Copyright 2019 George Racinet <georges.racinet@octobus.net>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import errno
12 12 import re
13 13 import struct
14 14
15 15 from ..node import hex
16 16
17 17 from .. import (
18 18 error,
19 19 util,
20 20 )
21 21 from . import docket as docket_mod
22 22
23 23
24 24 class NodeMap(dict):
25 25 def __missing__(self, x):
26 26 raise error.RevlogError(b'unknown node: %s' % x)
27 27
28 28
29 def test_race_hook_1():
30 """hook point for test
31
32 This let tests to have things happens between the docket reading and the
33 data reading"""
34 pass
35
36
29 37 def persisted_data(revlog):
30 38 """read the nodemap for a revlog from disk"""
31 39 if revlog._nodemap_file is None:
32 40 return None
33 41 pdata = revlog.opener.tryread(revlog._nodemap_file)
34 42 if not pdata:
35 43 return None
36 44 offset = 0
37 45 (version,) = S_VERSION.unpack(pdata[offset : offset + S_VERSION.size])
38 46 if version != ONDISK_VERSION:
39 47 return None
40 48 offset += S_VERSION.size
41 49 headers = S_HEADER.unpack(pdata[offset : offset + S_HEADER.size])
42 50 uid_size, tip_rev, data_length, data_unused, tip_node_size = headers
43 51 offset += S_HEADER.size
44 52 docket = NodeMapDocket(pdata[offset : offset + uid_size])
45 53 offset += uid_size
46 54 docket.tip_rev = tip_rev
47 55 docket.tip_node = pdata[offset : offset + tip_node_size]
48 56 docket.data_length = data_length
49 57 docket.data_unused = data_unused
50 58
51 59 filename = _rawdata_filepath(revlog, docket)
52 60 use_mmap = revlog.opener.options.get(b"persistent-nodemap.mmap")
61
62 test_race_hook_1()
53 63 try:
54 64 with revlog.opener(filename) as fd:
55 65 if use_mmap:
56 66 try:
57 67 data = util.buffer(util.mmapread(fd, data_length))
58 68 except ValueError:
59 69 # raised when the read file is too small
60 70 data = b''
61 71 else:
62 72 data = fd.read(data_length)
63 73 except (IOError, OSError) as e:
64 74 if e.errno == errno.ENOENT:
65 75 return None
66 76 else:
67 77 raise
68 78 if len(data) < data_length:
69 79 return None
70 80 return docket, data
71 81
72 82
73 83 def setup_persistent_nodemap(tr, revlog):
74 84 """Install whatever is needed transaction side to persist a nodemap on disk
75 85
76 86 (only actually persist the nodemap if this is relevant for this revlog)
77 87 """
78 88 if revlog._inline:
79 89 return # inlined revlog are too small for this to be relevant
80 90 if revlog._nodemap_file is None:
81 91 return # we do not use persistent_nodemap on this revlog
82 92
83 93 # we need to happen after the changelog finalization, in that use "cl-"
84 94 callback_id = b"nm-revlog-persistent-nodemap-%s" % revlog._nodemap_file
85 95 if tr.hasfinalize(callback_id):
86 96 return # no need to register again
87 97 tr.addpending(
88 98 callback_id, lambda tr: persist_nodemap(tr, revlog, pending=True)
89 99 )
90 100 tr.addfinalize(callback_id, lambda tr: persist_nodemap(tr, revlog))
91 101
92 102
93 103 class _NoTransaction(object):
94 104 """transaction like object to update the nodemap outside a transaction"""
95 105
96 106 def __init__(self):
97 107 self._postclose = {}
98 108
99 109 def addpostclose(self, callback_id, callback_func):
100 110 self._postclose[callback_id] = callback_func
101 111
102 112 def registertmp(self, *args, **kwargs):
103 113 pass
104 114
105 115 def addbackup(self, *args, **kwargs):
106 116 pass
107 117
108 118 def add(self, *args, **kwargs):
109 119 pass
110 120
111 121 def addabort(self, *args, **kwargs):
112 122 pass
113 123
114 124 def _report(self, *args):
115 125 pass
116 126
117 127
118 128 def update_persistent_nodemap(revlog):
119 129 """update the persistent nodemap right now
120 130
121 131 To be used for updating the nodemap on disk outside of a normal transaction
122 132 setup (eg, `debugupdatecache`).
123 133 """
124 134 if revlog._inline:
125 135 return # inlined revlog are too small for this to be relevant
126 136 if revlog._nodemap_file is None:
127 137 return # we do not use persistent_nodemap on this revlog
128 138
129 139 notr = _NoTransaction()
130 140 persist_nodemap(notr, revlog)
131 141 for k in sorted(notr._postclose):
132 142 notr._postclose[k](None)
133 143
134 144
135 145 def delete_nodemap(tr, repo, revlog):
136 146 """Delete nodemap data on disk for a given revlog"""
137 147 if revlog._nodemap_file is None:
138 148 msg = "calling persist nodemap on a revlog without the feature enabled"
139 149 raise error.ProgrammingError(msg)
140 150 repo.svfs.unlink(revlog._nodemap_file)
141 151
142 152
143 153 def persist_nodemap(tr, revlog, pending=False, force=False):
144 154 """Write nodemap data on disk for a given revlog"""
145 155 if getattr(revlog, 'filteredrevs', ()):
146 156 raise error.ProgrammingError(
147 157 "cannot persist nodemap of a filtered changelog"
148 158 )
149 159 if revlog._nodemap_file is None:
150 160 if force:
151 161 revlog._nodemap_file = get_nodemap_file(revlog)
152 162 else:
153 163 msg = "calling persist nodemap on a revlog without the feature enabled"
154 164 raise error.ProgrammingError(msg)
155 165
156 166 can_incremental = util.safehasattr(revlog.index, "nodemap_data_incremental")
157 167 ondisk_docket = revlog._nodemap_docket
158 168 feed_data = util.safehasattr(revlog.index, "update_nodemap_data")
159 169 use_mmap = revlog.opener.options.get(b"persistent-nodemap.mmap")
160 170
161 171 data = None
162 172 # first attemp an incremental update of the data
163 173 if can_incremental and ondisk_docket is not None:
164 174 target_docket = revlog._nodemap_docket.copy()
165 175 (
166 176 src_docket,
167 177 data_changed_count,
168 178 data,
169 179 ) = revlog.index.nodemap_data_incremental()
170 180 new_length = target_docket.data_length + len(data)
171 181 new_unused = target_docket.data_unused + data_changed_count
172 182 if src_docket != target_docket:
173 183 data = None
174 184 elif new_length <= (new_unused * 10): # under 10% of unused data
175 185 data = None
176 186 else:
177 187 datafile = _rawdata_filepath(revlog, target_docket)
178 188 # EXP-TODO: if this is a cache, this should use a cache vfs, not a
179 189 # store vfs
180 190 tr.add(datafile, target_docket.data_length)
181 191 with revlog.opener(datafile, b'r+') as fd:
182 192 fd.seek(target_docket.data_length)
183 193 fd.write(data)
184 194 if feed_data:
185 195 if use_mmap:
186 196 fd.seek(0)
187 197 new_data = fd.read(new_length)
188 198 else:
189 199 fd.flush()
190 200 new_data = util.buffer(util.mmapread(fd, new_length))
191 201 target_docket.data_length = new_length
192 202 target_docket.data_unused = new_unused
193 203
194 204 if data is None:
195 205 # otherwise fallback to a full new export
196 206 target_docket = NodeMapDocket()
197 207 datafile = _rawdata_filepath(revlog, target_docket)
198 208 if util.safehasattr(revlog.index, "nodemap_data_all"):
199 209 data = revlog.index.nodemap_data_all()
200 210 else:
201 211 data = persistent_data(revlog.index)
202 212 # EXP-TODO: if this is a cache, this should use a cache vfs, not a
203 213 # store vfs
204 214
205 215 tryunlink = revlog.opener.tryunlink
206 216
207 217 def abortck(tr):
208 218 tryunlink(datafile)
209 219
210 220 callback_id = b"delete-%s" % datafile
211 221
212 222 # some flavor of the transaction abort does not cleanup new file, it
213 223 # simply empty them.
214 224 tr.addabort(callback_id, abortck)
215 225 with revlog.opener(datafile, b'w+') as fd:
216 226 fd.write(data)
217 227 if feed_data:
218 228 if use_mmap:
219 229 new_data = data
220 230 else:
221 231 fd.flush()
222 232 new_data = util.buffer(util.mmapread(fd, len(data)))
223 233 target_docket.data_length = len(data)
224 234 target_docket.tip_rev = revlog.tiprev()
225 235 target_docket.tip_node = revlog.node(target_docket.tip_rev)
226 236 # EXP-TODO: if this is a cache, this should use a cache vfs, not a
227 237 # store vfs
228 238 file_path = revlog._nodemap_file
229 239 if pending:
230 240 file_path += b'.a'
231 241 tr.registertmp(file_path)
232 242 else:
233 243 tr.addbackup(file_path)
234 244
235 245 with revlog.opener(file_path, b'w', atomictemp=True) as fp:
236 246 fp.write(target_docket.serialize())
237 247 revlog._nodemap_docket = target_docket
238 248 if feed_data:
239 249 revlog.index.update_nodemap_data(target_docket, new_data)
240 250
241 251 # search for old index file in all cases, some older process might have
242 252 # left one behind.
243 253 olds = _other_rawdata_filepath(revlog, target_docket)
244 254 if olds:
245 255 realvfs = getattr(revlog, '_realopener', revlog.opener)
246 256
247 257 def cleanup(tr):
248 258 for oldfile in olds:
249 259 realvfs.tryunlink(oldfile)
250 260
251 261 callback_id = b"revlog-cleanup-nodemap-%s" % revlog._nodemap_file
252 262 tr.addpostclose(callback_id, cleanup)
253 263
254 264
255 265 ### Nodemap docket file
256 266 #
257 267 # The nodemap data are stored on disk using 2 files:
258 268 #
259 269 # * a raw data files containing a persistent nodemap
260 270 # (see `Nodemap Trie` section)
261 271 #
262 272 # * a small "docket" file containing medatadata
263 273 #
264 274 # While the nodemap data can be multiple tens of megabytes, the "docket" is
265 275 # small, it is easy to update it automatically or to duplicated its content
266 276 # during a transaction.
267 277 #
268 278 # Multiple raw data can exist at the same time (The currently valid one and a
269 279 # new one beind used by an in progress transaction). To accomodate this, the
270 280 # filename hosting the raw data has a variable parts. The exact filename is
271 281 # specified inside the "docket" file.
272 282 #
273 283 # The docket file contains information to find, qualify and validate the raw
274 284 # data. Its content is currently very light, but it will expand as the on disk
275 285 # nodemap gains the necessary features to be used in production.
276 286
277 287 ONDISK_VERSION = 1
278 288 S_VERSION = struct.Struct(">B")
279 289 S_HEADER = struct.Struct(">BQQQQ")
280 290
281 291
282 292 class NodeMapDocket(object):
283 293 """metadata associated with persistent nodemap data
284 294
285 295 The persistent data may come from disk or be on their way to disk.
286 296 """
287 297
288 298 def __init__(self, uid=None):
289 299 if uid is None:
290 300 uid = docket_mod.make_uid()
291 301 # a unique identifier for the data file:
292 302 # - When new data are appended, it is preserved.
293 303 # - When a new data file is created, a new identifier is generated.
294 304 self.uid = uid
295 305 # the tipmost revision stored in the data file. This revision and all
296 306 # revision before it are expected to be encoded in the data file.
297 307 self.tip_rev = None
298 308 # the node of that tipmost revision, if it mismatch the current index
299 309 # data the docket is not valid for the current index and should be
300 310 # discarded.
301 311 #
302 312 # note: this method is not perfect as some destructive operation could
303 313 # preserve the same tip_rev + tip_node while altering lower revision.
304 314 # However this multiple other caches have the same vulnerability (eg:
305 315 # brancmap cache).
306 316 self.tip_node = None
307 317 # the size (in bytes) of the persisted data to encode the nodemap valid
308 318 # for `tip_rev`.
309 319 # - data file shorter than this are corrupted,
310 320 # - any extra data should be ignored.
311 321 self.data_length = None
312 322 # the amount (in bytes) of "dead" data, still in the data file but no
313 323 # longer used for the nodemap.
314 324 self.data_unused = 0
315 325
316 326 def copy(self):
317 327 new = NodeMapDocket(uid=self.uid)
318 328 new.tip_rev = self.tip_rev
319 329 new.tip_node = self.tip_node
320 330 new.data_length = self.data_length
321 331 new.data_unused = self.data_unused
322 332 return new
323 333
324 334 def __cmp__(self, other):
325 335 if self.uid < other.uid:
326 336 return -1
327 337 if self.uid > other.uid:
328 338 return 1
329 339 elif self.data_length < other.data_length:
330 340 return -1
331 341 elif self.data_length > other.data_length:
332 342 return 1
333 343 return 0
334 344
335 345 def __eq__(self, other):
336 346 return self.uid == other.uid and self.data_length == other.data_length
337 347
338 348 def serialize(self):
339 349 """return serialized bytes for a docket using the passed uid"""
340 350 data = []
341 351 data.append(S_VERSION.pack(ONDISK_VERSION))
342 352 headers = (
343 353 len(self.uid),
344 354 self.tip_rev,
345 355 self.data_length,
346 356 self.data_unused,
347 357 len(self.tip_node),
348 358 )
349 359 data.append(S_HEADER.pack(*headers))
350 360 data.append(self.uid)
351 361 data.append(self.tip_node)
352 362 return b''.join(data)
353 363
354 364
355 365 def _rawdata_filepath(revlog, docket):
356 366 """The (vfs relative) nodemap's rawdata file for a given uid"""
357 367 prefix = revlog.radix
358 368 return b"%s-%s.nd" % (prefix, docket.uid)
359 369
360 370
361 371 def _other_rawdata_filepath(revlog, docket):
362 372 prefix = revlog.radix
363 373 pattern = re.compile(br"(^|/)%s-[0-9a-f]+\.nd$" % prefix)
364 374 new_file_path = _rawdata_filepath(revlog, docket)
365 375 new_file_name = revlog.opener.basename(new_file_path)
366 376 dirpath = revlog.opener.dirname(new_file_path)
367 377 others = []
368 378 for f in revlog.opener.listdir(dirpath):
369 379 if pattern.match(f) and f != new_file_name:
370 380 others.append(f)
371 381 return others
372 382
373 383
374 384 ### Nodemap Trie
375 385 #
376 386 # This is a simple reference implementation to compute and persist a nodemap
377 387 # trie. This reference implementation is write only. The python version of this
378 388 # is not expected to be actually used, since it wont provide performance
379 389 # improvement over existing non-persistent C implementation.
380 390 #
381 391 # The nodemap is persisted as Trie using 4bits-address/16-entries block. each
382 392 # revision can be adressed using its node shortest prefix.
383 393 #
384 394 # The trie is stored as a sequence of block. Each block contains 16 entries
385 395 # (signed 64bit integer, big endian). Each entry can be one of the following:
386 396 #
387 397 # * value >= 0 -> index of sub-block
388 398 # * value == -1 -> no value
389 399 # * value < -1 -> encoded revision: rev = -(value+2)
390 400 #
391 401 # See REV_OFFSET and _transform_rev below.
392 402 #
393 403 # The implementation focus on simplicity, not on performance. A Rust
394 404 # implementation should provide a efficient version of the same binary
395 405 # persistence. This reference python implementation is never meant to be
396 406 # extensively use in production.
397 407
398 408
399 409 def persistent_data(index):
400 410 """return the persistent binary form for a nodemap for a given index"""
401 411 trie = _build_trie(index)
402 412 return _persist_trie(trie)
403 413
404 414
405 415 def update_persistent_data(index, root, max_idx, last_rev):
406 416 """return the incremental update for persistent nodemap from a given index"""
407 417 changed_block, trie = _update_trie(index, root, last_rev)
408 418 return (
409 419 changed_block * S_BLOCK.size,
410 420 _persist_trie(trie, existing_idx=max_idx),
411 421 )
412 422
413 423
414 424 S_BLOCK = struct.Struct(">" + ("l" * 16))
415 425
416 426 NO_ENTRY = -1
417 427 # rev 0 need to be -2 because 0 is used by block, -1 is a special value.
418 428 REV_OFFSET = 2
419 429
420 430
421 431 def _transform_rev(rev):
422 432 """Return the number used to represent the rev in the tree.
423 433
424 434 (or retrieve a rev number from such representation)
425 435
426 436 Note that this is an involution, a function equal to its inverse (i.e.
427 437 which gives the identity when applied to itself).
428 438 """
429 439 return -(rev + REV_OFFSET)
430 440
431 441
432 442 def _to_int(hex_digit):
433 443 """turn an hexadecimal digit into a proper integer"""
434 444 return int(hex_digit, 16)
435 445
436 446
437 447 class Block(dict):
438 448 """represent a block of the Trie
439 449
440 450 contains up to 16 entry indexed from 0 to 15"""
441 451
442 452 def __init__(self):
443 453 super(Block, self).__init__()
444 454 # If this block exist on disk, here is its ID
445 455 self.ondisk_id = None
446 456
447 457 def __iter__(self):
448 458 return iter(self.get(i) for i in range(16))
449 459
450 460
451 461 def _build_trie(index):
452 462 """build a nodemap trie
453 463
454 464 The nodemap stores revision number for each unique prefix.
455 465
456 466 Each block is a dictionary with keys in `[0, 15]`. Values are either
457 467 another block or a revision number.
458 468 """
459 469 root = Block()
460 470 for rev in range(len(index)):
461 471 current_hex = hex(index[rev][7])
462 472 _insert_into_block(index, 0, root, rev, current_hex)
463 473 return root
464 474
465 475
466 476 def _update_trie(index, root, last_rev):
467 477 """consume"""
468 478 changed = 0
469 479 for rev in range(last_rev + 1, len(index)):
470 480 current_hex = hex(index[rev][7])
471 481 changed += _insert_into_block(index, 0, root, rev, current_hex)
472 482 return changed, root
473 483
474 484
475 485 def _insert_into_block(index, level, block, current_rev, current_hex):
476 486 """insert a new revision in a block
477 487
478 488 index: the index we are adding revision for
479 489 level: the depth of the current block in the trie
480 490 block: the block currently being considered
481 491 current_rev: the revision number we are adding
482 492 current_hex: the hexadecimal representation of the of that revision
483 493 """
484 494 changed = 1
485 495 if block.ondisk_id is not None:
486 496 block.ondisk_id = None
487 497 hex_digit = _to_int(current_hex[level : level + 1])
488 498 entry = block.get(hex_digit)
489 499 if entry is None:
490 500 # no entry, simply store the revision number
491 501 block[hex_digit] = current_rev
492 502 elif isinstance(entry, dict):
493 503 # need to recurse to an underlying block
494 504 changed += _insert_into_block(
495 505 index, level + 1, entry, current_rev, current_hex
496 506 )
497 507 else:
498 508 # collision with a previously unique prefix, inserting new
499 509 # vertices to fit both entry.
500 510 other_hex = hex(index[entry][7])
501 511 other_rev = entry
502 512 new = Block()
503 513 block[hex_digit] = new
504 514 _insert_into_block(index, level + 1, new, other_rev, other_hex)
505 515 _insert_into_block(index, level + 1, new, current_rev, current_hex)
506 516 return changed
507 517
508 518
509 519 def _persist_trie(root, existing_idx=None):
510 520 """turn a nodemap trie into persistent binary data
511 521
512 522 See `_build_trie` for nodemap trie structure"""
513 523 block_map = {}
514 524 if existing_idx is not None:
515 525 base_idx = existing_idx + 1
516 526 else:
517 527 base_idx = 0
518 528 chunks = []
519 529 for tn in _walk_trie(root):
520 530 if tn.ondisk_id is not None:
521 531 block_map[id(tn)] = tn.ondisk_id
522 532 else:
523 533 block_map[id(tn)] = len(chunks) + base_idx
524 534 chunks.append(_persist_block(tn, block_map))
525 535 return b''.join(chunks)
526 536
527 537
528 538 def _walk_trie(block):
529 539 """yield all the block in a trie
530 540
531 541 Children blocks are always yield before their parent block.
532 542 """
533 543 for (__, item) in sorted(block.items()):
534 544 if isinstance(item, dict):
535 545 for sub_block in _walk_trie(item):
536 546 yield sub_block
537 547 yield block
538 548
539 549
540 550 def _persist_block(block_node, block_map):
541 551 """produce persistent binary data for a single block
542 552
543 553 Children block are assumed to be already persisted and present in
544 554 block_map.
545 555 """
546 556 data = tuple(_to_value(v, block_map) for v in block_node)
547 557 return S_BLOCK.pack(*data)
548 558
549 559
550 560 def _to_value(item, block_map):
551 561 """persist any value as an integer"""
552 562 if item is None:
553 563 return NO_ENTRY
554 564 elif isinstance(item, dict):
555 565 return block_map[id(item)]
556 566 else:
557 567 return _transform_rev(item)
558 568
559 569
560 570 def parse_data(data):
561 571 """parse parse nodemap data into a nodemap Trie"""
562 572 if (len(data) % S_BLOCK.size) != 0:
563 573 msg = b"nodemap data size is not a multiple of block size (%d): %d"
564 574 raise error.Abort(msg % (S_BLOCK.size, len(data)))
565 575 if not data:
566 576 return Block(), None
567 577 block_map = {}
568 578 new_blocks = []
569 579 for i in range(0, len(data), S_BLOCK.size):
570 580 block = Block()
571 581 block.ondisk_id = len(block_map)
572 582 block_map[block.ondisk_id] = block
573 583 block_data = data[i : i + S_BLOCK.size]
574 584 values = S_BLOCK.unpack(block_data)
575 585 new_blocks.append((block, values))
576 586 for b, values in new_blocks:
577 587 for idx, v in enumerate(values):
578 588 if v == NO_ENTRY:
579 589 continue
580 590 elif v >= 0:
581 591 b[idx] = block_map[v]
582 592 else:
583 593 b[idx] = _transform_rev(v)
584 594 return block, i // S_BLOCK.size
585 595
586 596
587 597 # debug utility
588 598
589 599
590 600 def check_data(ui, index, data):
591 601 """verify that the provided nodemap data are valid for the given idex"""
592 602 ret = 0
593 603 ui.status((b"revision in index: %d\n") % len(index))
594 604 root, __ = parse_data(data)
595 605 all_revs = set(_all_revisions(root))
596 606 ui.status((b"revision in nodemap: %d\n") % len(all_revs))
597 607 for r in range(len(index)):
598 608 if r not in all_revs:
599 609 msg = b" revision missing from nodemap: %d\n" % r
600 610 ui.write_err(msg)
601 611 ret = 1
602 612 else:
603 613 all_revs.remove(r)
604 614 nm_rev = _find_node(root, hex(index[r][7]))
605 615 if nm_rev is None:
606 616 msg = b" revision node does not match any entries: %d\n" % r
607 617 ui.write_err(msg)
608 618 ret = 1
609 619 elif nm_rev != r:
610 620 msg = (
611 621 b" revision node does not match the expected revision: "
612 622 b"%d != %d\n" % (r, nm_rev)
613 623 )
614 624 ui.write_err(msg)
615 625 ret = 1
616 626
617 627 if all_revs:
618 628 for r in sorted(all_revs):
619 629 msg = b" extra revision in nodemap: %d\n" % r
620 630 ui.write_err(msg)
621 631 ret = 1
622 632 return ret
623 633
624 634
625 635 def _all_revisions(root):
626 636 """return all revisions stored in a Trie"""
627 637 for block in _walk_trie(root):
628 638 for v in block:
629 639 if v is None or isinstance(v, Block):
630 640 continue
631 641 yield v
632 642
633 643
634 644 def _find_node(block, node):
635 645 """find the revision associated with a given node"""
636 646 entry = block.get(_to_int(node[0:1]))
637 647 if isinstance(entry, dict):
638 648 return _find_node(entry, node[1:])
639 649 return entry
640 650
641 651
642 652 def get_nodemap_file(revlog):
643 653 if revlog._trypending:
644 654 pending_path = revlog.radix + b".n.a"
645 655 if revlog.opener.exists(pending_path):
646 656 return pending_path
647 657 return revlog.radix + b".n"
@@ -1,1099 +1,1297 b''
1 1 ===================================
2 2 Test the persistent on-disk nodemap
3 3 ===================================
4 4
5 5
6 6 $ cat << EOF >> $HGRCPATH
7 7 > [format]
8 8 > use-share-safe=yes
9 9 > [extensions]
10 10 > share=
11 11 > EOF
12 12
13 13 #if no-rust
14 14
15 15 $ cat << EOF >> $HGRCPATH
16 16 > [format]
17 17 > use-persistent-nodemap=yes
18 18 > [devel]
19 19 > persistent-nodemap=yes
20 20 > EOF
21 21
22 22 #endif
23 23
24 24 $ hg init test-repo --config storage.revlog.persistent-nodemap.slow-path=allow
25 25 $ cd test-repo
26 26
27 27 Check handling of the default slow-path value
28 28
29 29 #if no-pure no-rust
30 30
31 31 $ hg id
32 32 abort: accessing `persistent-nodemap` repository without associated fast implementation.
33 33 (check `hg help config.format.use-persistent-nodemap` for details)
34 34 [255]
35 35
36 36 Unlock further check (we are here to test the feature)
37 37
38 38 $ cat << EOF >> $HGRCPATH
39 39 > [storage]
40 40 > # to avoid spamming the test
41 41 > revlog.persistent-nodemap.slow-path=allow
42 42 > EOF
43 43
44 44 #endif
45 45
46 46 #if rust
47 47
48 48 Regression test for a previous bug in Rust/C FFI for the `Revlog_CAPI` capsule:
49 49 in places where `mercurial/cext/revlog.c` function signatures use `Py_ssize_t`
50 50 (64 bits on Linux x86_64), corresponding declarations in `rust/hg-cpython/src/cindex.rs`
51 51 incorrectly used `libc::c_int` (32 bits).
52 52 As a result, -1 passed from Rust for the null revision became 4294967295 in C.
53 53
54 54 $ hg log -r 00000000
55 55 changeset: -1:000000000000
56 56 tag: tip
57 57 user:
58 58 date: Thu Jan 01 00:00:00 1970 +0000
59 59
60 60
61 61 #endif
62 62
63 63
64 64 $ hg debugformat
65 65 format-variant repo
66 66 fncache: yes
67 67 dirstate-v2: no
68 68 dotencode: yes
69 69 generaldelta: yes
70 70 share-safe: yes
71 71 sparserevlog: yes
72 72 persistent-nodemap: yes
73 73 copies-sdc: no
74 74 revlog-v2: no
75 75 changelog-v2: no
76 76 plain-cl-delta: yes
77 77 compression: zlib (no-zstd !)
78 78 compression: zstd (zstd !)
79 79 compression-level: default
80 80 $ hg debugbuilddag .+5000 --new-file
81 81
82 82 $ hg debugnodemap --metadata
83 83 uid: ???????? (glob)
84 84 tip-rev: 5000
85 85 tip-node: 6b02b8c7b96654c25e86ba69eda198d7e6ad8b3c
86 86 data-length: 121088
87 87 data-unused: 0
88 88 data-unused: 0.000%
89 89 $ f --size .hg/store/00changelog.n
90 90 .hg/store/00changelog.n: size=62
91 91
92 92 Simple lookup works
93 93
94 94 $ ANYNODE=`hg log --template '{node|short}\n' --rev tip`
95 95 $ hg log -r "$ANYNODE" --template '{rev}\n'
96 96 5000
97 97
98 98
99 99 #if rust
100 100
101 101 $ f --sha256 .hg/store/00changelog-*.nd
102 102 .hg/store/00changelog-????????.nd: sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd (glob)
103 103
104 104 $ f --sha256 .hg/store/00manifest-*.nd
105 105 .hg/store/00manifest-????????.nd: sha256=97117b1c064ea2f86664a124589e47db0e254e8d34739b5c5cc5bf31c9da2b51 (glob)
106 106 $ hg debugnodemap --dump-new | f --sha256 --size
107 107 size=121088, sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd
108 108 $ hg debugnodemap --dump-disk | f --sha256 --bytes=256 --hexdump --size
109 109 size=121088, sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd
110 110 0000: 00 00 00 91 00 00 00 20 00 00 00 bb 00 00 00 e7 |....... ........|
111 111 0010: 00 00 00 66 00 00 00 a1 00 00 01 13 00 00 01 22 |...f..........."|
112 112 0020: 00 00 00 23 00 00 00 fc 00 00 00 ba 00 00 00 5e |...#...........^|
113 113 0030: 00 00 00 df 00 00 01 4e 00 00 01 65 00 00 00 ab |.......N...e....|
114 114 0040: 00 00 00 a9 00 00 00 95 00 00 00 73 00 00 00 38 |...........s...8|
115 115 0050: 00 00 00 cc 00 00 00 92 00 00 00 90 00 00 00 69 |...............i|
116 116 0060: 00 00 00 ec 00 00 00 8d 00 00 01 4f 00 00 00 12 |...........O....|
117 117 0070: 00 00 02 0c 00 00 00 77 00 00 00 9c 00 00 00 8f |.......w........|
118 118 0080: 00 00 00 d5 00 00 00 6b 00 00 00 48 00 00 00 b3 |.......k...H....|
119 119 0090: 00 00 00 e5 00 00 00 b5 00 00 00 8e 00 00 00 ad |................|
120 120 00a0: 00 00 00 7b 00 00 00 7c 00 00 00 0b 00 00 00 2b |...{...|.......+|
121 121 00b0: 00 00 00 c6 00 00 00 1e 00 00 01 08 00 00 00 11 |................|
122 122 00c0: 00 00 01 30 00 00 00 26 00 00 01 9c 00 00 00 35 |...0...&.......5|
123 123 00d0: 00 00 00 b8 00 00 01 31 00 00 00 2c 00 00 00 55 |.......1...,...U|
124 124 00e0: 00 00 00 8a 00 00 00 9a 00 00 00 0c 00 00 01 1e |................|
125 125 00f0: 00 00 00 a4 00 00 00 83 00 00 00 c9 00 00 00 8c |................|
126 126
127 127
128 128 #else
129 129
130 130 $ f --sha256 .hg/store/00changelog-*.nd
131 131 .hg/store/00changelog-????????.nd: sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79 (glob)
132 132 $ hg debugnodemap --dump-new | f --sha256 --size
133 133 size=121088, sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79
134 134 $ hg debugnodemap --dump-disk | f --sha256 --bytes=256 --hexdump --size
135 135 size=121088, sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79
136 136 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
137 137 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
138 138 0020: ff ff ff ff ff ff f5 06 ff ff ff ff ff ff f3 e7 |................|
139 139 0030: ff ff ef ca ff ff ff ff ff ff ff ff ff ff ff ff |................|
140 140 0040: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
141 141 0050: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ed 08 |................|
142 142 0060: ff ff ed 66 ff ff ff ff ff ff ff ff ff ff ff ff |...f............|
143 143 0070: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
144 144 0080: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
145 145 0090: ff ff ff ff ff ff ff ff ff ff ff ff ff ff f6 ed |................|
146 146 00a0: ff ff ff ff ff ff fe 61 ff ff ff ff ff ff ff ff |.......a........|
147 147 00b0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
148 148 00c0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
149 149 00d0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
150 150 00e0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff f1 02 |................|
151 151 00f0: ff ff ff ff ff ff ed 1b ff ff ff ff ff ff ff ff |................|
152 152
153 153 #endif
154 154
155 155 $ hg debugnodemap --check
156 156 revision in index: 5001
157 157 revision in nodemap: 5001
158 158
159 159 add a new commit
160 160
161 161 $ hg up
162 162 5001 files updated, 0 files merged, 0 files removed, 0 files unresolved
163 163 $ echo foo > foo
164 164 $ hg add foo
165 165
166 166
167 167 Check slow-path config value handling
168 168 -------------------------------------
169 169
170 170 #if no-pure no-rust
171 171
172 172 $ hg id --config "storage.revlog.persistent-nodemap.slow-path=invalid-value"
173 173 unknown value for config "storage.revlog.persistent-nodemap.slow-path": "invalid-value"
174 174 falling back to default value: abort
175 175 abort: accessing `persistent-nodemap` repository without associated fast implementation.
176 176 (check `hg help config.format.use-persistent-nodemap` for details)
177 177 [255]
178 178
179 179 $ hg log -r . --config "storage.revlog.persistent-nodemap.slow-path=warn"
180 180 warning: accessing `persistent-nodemap` repository without associated fast implementation.
181 181 (check `hg help config.format.use-persistent-nodemap` for details)
182 182 changeset: 5000:6b02b8c7b966
183 183 tag: tip
184 184 user: debugbuilddag
185 185 date: Thu Jan 01 01:23:20 1970 +0000
186 186 summary: r5000
187 187
188 188 $ hg ci -m 'foo' --config "storage.revlog.persistent-nodemap.slow-path=abort"
189 189 abort: accessing `persistent-nodemap` repository without associated fast implementation.
190 190 (check `hg help config.format.use-persistent-nodemap` for details)
191 191 [255]
192 192
193 193 #else
194 194
195 195 $ hg id --config "storage.revlog.persistent-nodemap.slow-path=invalid-value"
196 196 unknown value for config "storage.revlog.persistent-nodemap.slow-path": "invalid-value"
197 197 falling back to default value: abort
198 198 6b02b8c7b966+ tip
199 199
200 200 #endif
201 201
202 202 $ hg ci -m 'foo'
203 203
204 204 #if no-pure no-rust
205 205 $ hg debugnodemap --metadata
206 206 uid: ???????? (glob)
207 207 tip-rev: 5001
208 208 tip-node: 16395c3cf7e231394735e6b1717823ada303fb0c
209 209 data-length: 121088
210 210 data-unused: 0
211 211 data-unused: 0.000%
212 212 #else
213 213 $ hg debugnodemap --metadata
214 214 uid: ???????? (glob)
215 215 tip-rev: 5001
216 216 tip-node: 16395c3cf7e231394735e6b1717823ada303fb0c
217 217 data-length: 121344
218 218 data-unused: 256
219 219 data-unused: 0.211%
220 220 #endif
221 221
222 222 $ f --size .hg/store/00changelog.n
223 223 .hg/store/00changelog.n: size=62
224 224
225 225 (The pure code use the debug code that perform incremental update, the C code reencode from scratch)
226 226
227 227 #if pure
228 228 $ f --sha256 .hg/store/00changelog-*.nd --size
229 229 .hg/store/00changelog-????????.nd: size=121344, sha256=cce54c5da5bde3ad72a4938673ed4064c86231b9c64376b082b163fdb20f8f66 (glob)
230 230 #endif
231 231
232 232 #if rust
233 233 $ f --sha256 .hg/store/00changelog-*.nd --size
234 234 .hg/store/00changelog-????????.nd: size=121344, sha256=952b042fcf614ceb37b542b1b723e04f18f83efe99bee4e0f5ccd232ef470e58 (glob)
235 235 #endif
236 236
237 237 #if no-pure no-rust
238 238 $ f --sha256 .hg/store/00changelog-*.nd --size
239 239 .hg/store/00changelog-????????.nd: size=121088, sha256=df7c06a035b96cb28c7287d349d603baef43240be7736fe34eea419a49702e17 (glob)
240 240 #endif
241 241
242 242 $ hg debugnodemap --check
243 243 revision in index: 5002
244 244 revision in nodemap: 5002
245 245
246 246 Test code path without mmap
247 247 ---------------------------
248 248
249 249 $ echo bar > bar
250 250 $ hg add bar
251 251 $ hg ci -m 'bar' --config storage.revlog.persistent-nodemap.mmap=no
252 252
253 253 $ hg debugnodemap --check --config storage.revlog.persistent-nodemap.mmap=yes
254 254 revision in index: 5003
255 255 revision in nodemap: 5003
256 256 $ hg debugnodemap --check --config storage.revlog.persistent-nodemap.mmap=no
257 257 revision in index: 5003
258 258 revision in nodemap: 5003
259 259
260 260
261 261 #if pure
262 262 $ hg debugnodemap --metadata
263 263 uid: ???????? (glob)
264 264 tip-rev: 5002
265 265 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
266 266 data-length: 121600
267 267 data-unused: 512
268 268 data-unused: 0.421%
269 269 $ f --sha256 .hg/store/00changelog-*.nd --size
270 270 .hg/store/00changelog-????????.nd: size=121600, sha256=def52503d049ccb823974af313a98a935319ba61f40f3aa06a8be4d35c215054 (glob)
271 271 #endif
272 272 #if rust
273 273 $ hg debugnodemap --metadata
274 274 uid: ???????? (glob)
275 275 tip-rev: 5002
276 276 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
277 277 data-length: 121600
278 278 data-unused: 512
279 279 data-unused: 0.421%
280 280 $ f --sha256 .hg/store/00changelog-*.nd --size
281 281 .hg/store/00changelog-????????.nd: size=121600, sha256=dacf5b5f1d4585fee7527d0e67cad5b1ba0930e6a0928f650f779aefb04ce3fb (glob)
282 282 #endif
283 283 #if no-pure no-rust
284 284 $ hg debugnodemap --metadata
285 285 uid: ???????? (glob)
286 286 tip-rev: 5002
287 287 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
288 288 data-length: 121088
289 289 data-unused: 0
290 290 data-unused: 0.000%
291 291 $ f --sha256 .hg/store/00changelog-*.nd --size
292 292 .hg/store/00changelog-????????.nd: size=121088, sha256=59fcede3e3cc587755916ceed29e3c33748cd1aa7d2f91828ac83e7979d935e8 (glob)
293 293 #endif
294 294
295 295 Test force warming the cache
296 296
297 297 $ rm .hg/store/00changelog.n
298 298 $ hg debugnodemap --metadata
299 299 $ hg debugupdatecache
300 300 #if pure
301 301 $ hg debugnodemap --metadata
302 302 uid: ???????? (glob)
303 303 tip-rev: 5002
304 304 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
305 305 data-length: 121088
306 306 data-unused: 0
307 307 data-unused: 0.000%
308 308 #else
309 309 $ hg debugnodemap --metadata
310 310 uid: ???????? (glob)
311 311 tip-rev: 5002
312 312 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
313 313 data-length: 121088
314 314 data-unused: 0
315 315 data-unused: 0.000%
316 316 #endif
317 317
318 318 Check out of sync nodemap
319 319 =========================
320 320
321 321 First copy old data on the side.
322 322
323 323 $ mkdir ../tmp-copies
324 324 $ cp .hg/store/00changelog-????????.nd .hg/store/00changelog.n ../tmp-copies
325 325
326 326 Nodemap lagging behind
327 327 ----------------------
328 328
329 329 make a new commit
330 330
331 331 $ echo bar2 > bar
332 332 $ hg ci -m 'bar2'
333 333 $ NODE=`hg log -r tip -T '{node}\n'`
334 334 $ hg log -r "$NODE" -T '{rev}\n'
335 335 5003
336 336
337 337 If the nodemap is lagging behind, it can catch up fine
338 338
339 339 $ hg debugnodemap --metadata
340 340 uid: ???????? (glob)
341 341 tip-rev: 5003
342 342 tip-node: c9329770f979ade2d16912267c38ba5f82fd37b3
343 343 data-length: 121344 (pure !)
344 344 data-length: 121344 (rust !)
345 345 data-length: 121152 (no-rust no-pure !)
346 346 data-unused: 192 (pure !)
347 347 data-unused: 192 (rust !)
348 348 data-unused: 0 (no-rust no-pure !)
349 349 data-unused: 0.158% (pure !)
350 350 data-unused: 0.158% (rust !)
351 351 data-unused: 0.000% (no-rust no-pure !)
352 352 $ cp -f ../tmp-copies/* .hg/store/
353 353 $ hg debugnodemap --metadata
354 354 uid: ???????? (glob)
355 355 tip-rev: 5002
356 356 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
357 357 data-length: 121088
358 358 data-unused: 0
359 359 data-unused: 0.000%
360 360 $ hg log -r "$NODE" -T '{rev}\n'
361 361 5003
362 362
363 363 changelog altered
364 364 -----------------
365 365
366 366 If the nodemap is not gated behind a requirements, an unaware client can alter
367 367 the repository so the revlog used to generate the nodemap is not longer
368 368 compatible with the persistent nodemap. We need to detect that.
369 369
370 370 $ hg up "$NODE~5"
371 371 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
372 372 $ echo bar > babar
373 373 $ hg add babar
374 374 $ hg ci -m 'babar'
375 375 created new head
376 376 $ OTHERNODE=`hg log -r tip -T '{node}\n'`
377 377 $ hg log -r "$OTHERNODE" -T '{rev}\n'
378 378 5004
379 379
380 380 $ hg --config extensions.strip= strip --rev "$NODE~1" --no-backup
381 381
382 382 the nodemap should detect the changelog have been tampered with and recover.
383 383
384 384 $ hg debugnodemap --metadata
385 385 uid: ???????? (glob)
386 386 tip-rev: 5002
387 387 tip-node: b355ef8adce0949b8bdf6afc72ca853740d65944
388 388 data-length: 121536 (pure !)
389 389 data-length: 121088 (rust !)
390 390 data-length: 121088 (no-pure no-rust !)
391 391 data-unused: 448 (pure !)
392 392 data-unused: 0 (rust !)
393 393 data-unused: 0 (no-pure no-rust !)
394 394 data-unused: 0.000% (rust !)
395 395 data-unused: 0.369% (pure !)
396 396 data-unused: 0.000% (no-pure no-rust !)
397 397
398 398 $ cp -f ../tmp-copies/* .hg/store/
399 399 $ hg debugnodemap --metadata
400 400 uid: ???????? (glob)
401 401 tip-rev: 5002
402 402 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
403 403 data-length: 121088
404 404 data-unused: 0
405 405 data-unused: 0.000%
406 406 $ hg log -r "$OTHERNODE" -T '{rev}\n'
407 407 5002
408 408
409 409 missing data file
410 410 -----------------
411 411
412 412 $ UUID=`hg debugnodemap --metadata| grep 'uid:' | \
413 413 > sed 's/uid: //'`
414 414 $ FILE=.hg/store/00changelog-"${UUID}".nd
415 415 $ mv $FILE ../tmp-data-file
416 416 $ cp .hg/store/00changelog.n ../tmp-docket
417 417
418 418 mercurial don't crash
419 419
420 420 $ hg log -r .
421 421 changeset: 5002:b355ef8adce0
422 422 tag: tip
423 423 parent: 4998:d918ad6d18d3
424 424 user: test
425 425 date: Thu Jan 01 00:00:00 1970 +0000
426 426 summary: babar
427 427
428 428 $ hg debugnodemap --metadata
429 429
430 430 $ hg debugupdatecache
431 431 $ hg debugnodemap --metadata
432 432 uid: * (glob)
433 433 tip-rev: 5002
434 434 tip-node: b355ef8adce0949b8bdf6afc72ca853740d65944
435 435 data-length: 121088
436 436 data-unused: 0
437 437 data-unused: 0.000%
438 438
439 439 Sub-case: fallback for corrupted data file
440 440 ------------------------------------------
441 441
442 442 Sabotaging the data file so that nodemap resolutions fail, triggering fallback to
443 443 (non-persistent) C implementation.
444 444
445 445
446 446 $ UUID=`hg debugnodemap --metadata| grep 'uid:' | \
447 447 > sed 's/uid: //'`
448 448 $ FILE=.hg/store/00changelog-"${UUID}".nd
449 449 $ python -c "fobj = open('$FILE', 'r+b'); fobj.write(b'\xff' * 121088); fobj.close()"
450 450
451 451 The nodemap data file is still considered in sync with the docket. This
452 452 would fail without the fallback to the (non-persistent) C implementation:
453 453
454 454 $ hg log -r b355ef8adce0949b8bdf6afc72ca853740d65944 -T '{rev}\n' --traceback
455 455 5002
456 456
457 457 The nodemap data file hasn't been fixed, more tests can be inserted:
458 458
459 459 $ hg debugnodemap --dump-disk | f --bytes=256 --hexdump --size
460 460 size=121088
461 461 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
462 462 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
463 463 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
464 464 0030: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
465 465 0040: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
466 466 0050: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
467 467 0060: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
468 468 0070: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
469 469 0080: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
470 470 0090: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
471 471 00a0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
472 472 00b0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
473 473 00c0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
474 474 00d0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
475 475 00e0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
476 476 00f0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
477 477
478 478 $ mv ../tmp-data-file $FILE
479 479 $ mv ../tmp-docket .hg/store/00changelog.n
480 480
481 481 Check transaction related property
482 482 ==================================
483 483
484 484 An up to date nodemap should be available to shell hooks,
485 485
486 486 $ echo dsljfl > a
487 487 $ hg add a
488 488 $ hg ci -m a
489 489 $ hg debugnodemap --metadata
490 490 uid: ???????? (glob)
491 491 tip-rev: 5003
492 492 tip-node: a52c5079765b5865d97b993b303a18740113bbb2
493 493 data-length: 121088
494 494 data-unused: 0
495 495 data-unused: 0.000%
496 496 $ echo babar2 > babar
497 497 $ hg ci -m 'babar2' --config "hooks.pretxnclose.nodemap-test=hg debugnodemap --metadata"
498 498 uid: ???????? (glob)
499 499 tip-rev: 5004
500 500 tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984
501 501 data-length: 121280 (pure !)
502 502 data-length: 121280 (rust !)
503 503 data-length: 121088 (no-pure no-rust !)
504 504 data-unused: 192 (pure !)
505 505 data-unused: 192 (rust !)
506 506 data-unused: 0 (no-pure no-rust !)
507 507 data-unused: 0.158% (pure !)
508 508 data-unused: 0.158% (rust !)
509 509 data-unused: 0.000% (no-pure no-rust !)
510 510 $ hg debugnodemap --metadata
511 511 uid: ???????? (glob)
512 512 tip-rev: 5004
513 513 tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984
514 514 data-length: 121280 (pure !)
515 515 data-length: 121280 (rust !)
516 516 data-length: 121088 (no-pure no-rust !)
517 517 data-unused: 192 (pure !)
518 518 data-unused: 192 (rust !)
519 519 data-unused: 0 (no-pure no-rust !)
520 520 data-unused: 0.158% (pure !)
521 521 data-unused: 0.158% (rust !)
522 522 data-unused: 0.000% (no-pure no-rust !)
523 523
524 524 Another process does not see the pending nodemap content during run.
525 525
526 526 $ echo qpoasp > a
527 527 $ hg ci -m a2 \
528 528 > --config "hooks.pretxnclose=sh \"$RUNTESTDIR/testlib/wait-on-file\" 20 sync-repo-read sync-txn-pending" \
529 529 > --config "hooks.txnclose=touch sync-txn-close" > output.txt 2>&1 &
530 530
531 531 (read the repository while the commit transaction is pending)
532 532
533 533 $ sh "$RUNTESTDIR/testlib/wait-on-file" 20 sync-txn-pending && \
534 534 > hg debugnodemap --metadata && \
535 535 > sh "$RUNTESTDIR/testlib/wait-on-file" 20 sync-txn-close sync-repo-read
536 536 uid: ???????? (glob)
537 537 tip-rev: 5004
538 538 tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984
539 539 data-length: 121280 (pure !)
540 540 data-length: 121280 (rust !)
541 541 data-length: 121088 (no-pure no-rust !)
542 542 data-unused: 192 (pure !)
543 543 data-unused: 192 (rust !)
544 544 data-unused: 0 (no-pure no-rust !)
545 545 data-unused: 0.158% (pure !)
546 546 data-unused: 0.158% (rust !)
547 547 data-unused: 0.000% (no-pure no-rust !)
548 548 $ hg debugnodemap --metadata
549 549 uid: ???????? (glob)
550 550 tip-rev: 5005
551 551 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
552 552 data-length: 121536 (pure !)
553 553 data-length: 121536 (rust !)
554 554 data-length: 121088 (no-pure no-rust !)
555 555 data-unused: 448 (pure !)
556 556 data-unused: 448 (rust !)
557 557 data-unused: 0 (no-pure no-rust !)
558 558 data-unused: 0.369% (pure !)
559 559 data-unused: 0.369% (rust !)
560 560 data-unused: 0.000% (no-pure no-rust !)
561 561
562 562 $ cat output.txt
563 563
564 564 Check that a failing transaction will properly revert the data
565 565
566 566 $ echo plakfe > a
567 567 $ f --size --sha256 .hg/store/00changelog-*.nd
568 568 .hg/store/00changelog-????????.nd: size=121536, sha256=bb414468d225cf52d69132e1237afba34d4346ee2eb81b505027e6197b107f03 (glob) (pure !)
569 569 .hg/store/00changelog-????????.nd: size=121536, sha256=909ac727bc4d1c0fda5f7bff3c620c98bd4a2967c143405a1503439e33b377da (glob) (rust !)
570 570 .hg/store/00changelog-????????.nd: size=121088, sha256=342d36d30d86dde67d3cb6c002606c4a75bcad665595d941493845066d9c8ee0 (glob) (no-pure no-rust !)
571 571 $ hg ci -m a3 --config "extensions.abort=$RUNTESTDIR/testlib/crash_transaction_late.py"
572 572 transaction abort!
573 573 rollback completed
574 574 abort: This is a late abort
575 575 [255]
576 576 $ hg debugnodemap --metadata
577 577 uid: ???????? (glob)
578 578 tip-rev: 5005
579 579 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
580 580 data-length: 121536 (pure !)
581 581 data-length: 121536 (rust !)
582 582 data-length: 121088 (no-pure no-rust !)
583 583 data-unused: 448 (pure !)
584 584 data-unused: 448 (rust !)
585 585 data-unused: 0 (no-pure no-rust !)
586 586 data-unused: 0.369% (pure !)
587 587 data-unused: 0.369% (rust !)
588 588 data-unused: 0.000% (no-pure no-rust !)
589 589 $ f --size --sha256 .hg/store/00changelog-*.nd
590 590 .hg/store/00changelog-????????.nd: size=121536, sha256=bb414468d225cf52d69132e1237afba34d4346ee2eb81b505027e6197b107f03 (glob) (pure !)
591 591 .hg/store/00changelog-????????.nd: size=121536, sha256=909ac727bc4d1c0fda5f7bff3c620c98bd4a2967c143405a1503439e33b377da (glob) (rust !)
592 592 .hg/store/00changelog-????????.nd: size=121088, sha256=342d36d30d86dde67d3cb6c002606c4a75bcad665595d941493845066d9c8ee0 (glob) (no-pure no-rust !)
593 593
594 594 Check that removing content does not confuse the nodemap
595 595 --------------------------------------------------------
596 596
597 597 removing data with rollback
598 598
599 599 $ echo aso > a
600 600 $ hg ci -m a4
601 601 $ hg rollback
602 602 repository tip rolled back to revision 5005 (undo commit)
603 603 working directory now based on revision 5005
604 604 $ hg id -r .
605 605 90d5d3ba2fc4 tip
606 606
607 607 removing data with strip
608 608
609 609 $ echo aso > a
610 610 $ hg ci -m a4
611 611 $ hg --config extensions.strip= strip -r . --no-backup
612 612 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
613 613 $ hg id -r . --traceback
614 614 90d5d3ba2fc4 tip
615 615
616 (be a good citizen and regenerate the nodemap)
617 $ hg debugupdatecaches
618 $ hg debugnodemap --metadata
619 uid: * (glob)
620 tip-rev: 5005
621 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
622 data-length: 121088
623 data-unused: 0
624 data-unused: 0.000%
625
626 Check race condition when multiple process write new data to the repository
627 ---------------------------------------------------------------------------
628
629 In this test, we check that two writers touching the repositories will not
630 overwrite each other data. This test is prompted by the existent of issue6554.
631 Where a writer ended up using and outdated docket to update the repository. See
632 the dedicated extension for details on the race windows and read/write schedule
633 necessary to end up in this situation: testlib/persistent-nodemap-race-ext.py
634
635 The issue was initially observed on a server with a high push trafic, but it
636 can be reproduced using a share and two commiting process which seems simpler.
637
638 The test is Rust only as the other implementation does not use the same
639 read/write patterns.
640
641 $ cd ..
642
643 #if rust
644
645 $ cp -R test-repo race-repo
646 $ hg share race-repo ./other-wc --config format.use-share-safe=yes
647 updating working directory
648 5001 files updated, 0 files merged, 0 files removed, 0 files unresolved
649 $ hg debugformat -R ./race-repo | egrep 'share-safe|persistent-nodemap'
650 share-safe: yes
651 persistent-nodemap: yes
652 $ hg debugformat -R ./other-wc/ | egrep 'share-safe|persistent-nodemap'
653 share-safe: yes
654 persistent-nodemap: yes
655 $ hg -R ./other-wc update 'min(head())'
656 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
657 $ hg -R ./race-repo debugnodemap --metadata
658 uid: 43c37dde
659 tip-rev: 5005
660 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
661 data-length: 121088
662 data-unused: 0
663 data-unused: 0.000%
664 $ hg -R ./race-repo log -G -r 'head()'
665 @ changeset: 5005:90d5d3ba2fc4
666 | tag: tip
667 ~ user: test
668 date: Thu Jan 01 00:00:00 1970 +0000
669 summary: a2
670
671 o changeset: 5001:16395c3cf7e2
672 | user: test
673 ~ date: Thu Jan 01 00:00:00 1970 +0000
674 summary: foo
675
676 $ hg -R ./other-wc log -G -r 'head()'
677 o changeset: 5005:90d5d3ba2fc4
678 | tag: tip
679 ~ user: test
680 date: Thu Jan 01 00:00:00 1970 +0000
681 summary: a2
682
683 @ changeset: 5001:16395c3cf7e2
684 | user: test
685 ~ date: Thu Jan 01 00:00:00 1970 +0000
686 summary: foo
687
688 $ echo left-side-race > race-repo/left-side-race
689 $ hg -R ./race-repo/ add race-repo/left-side-race
690
691 $ echo right-side-race > ./other-wc/right-side-race
692 $ hg -R ./other-wc/ add ./other-wc/right-side-race
693
694 $ mkdir sync-files
695 $ mkdir outputs
696 $ (
697 > hg -R ./race-repo/ commit -m left-side-commit \
698 > --config "extensions.race=${RUNTESTDIR}/testlib/persistent-nodemap-race-ext.py" \
699 > --config 'devel.nodemap-race.role=left';
700 > touch sync-files/left-done
701 > ) > outputs/left.txt 2>&1 &
702 $ (
703 > hg -R ./other-wc/ commit -m right-side-commit \
704 > --config "extensions.race=${RUNTESTDIR}/testlib/persistent-nodemap-race-ext.py" \
705 > --config 'devel.nodemap-race.role=right';
706 > touch sync-files/right-done
707 > ) > outputs/right.txt 2>&1 &
708 $ (
709 > hg -R ./race-repo/ check-nodemap-race \
710 > --config "extensions.race=${RUNTESTDIR}/testlib/persistent-nodemap-race-ext.py" \
711 > --config 'devel.nodemap-race.role=reader';
712 > touch sync-files/reader-done
713 > ) > outputs/reader.txt 2>&1 &
714 $ sh "$RUNTESTDIR"/testlib/wait-on-file 10 sync-files/left-done
715 $ cat outputs/left.txt
716 docket-details:
717 uid: 43c37dde
718 actual-tip: 5005
719 tip-rev: 5005
720 data-length: 121088
721 nodemap-race: left side locked and ready to commit
722 docket-details:
723 uid: 43c37dde
724 actual-tip: 5005
725 tip-rev: 5005
726 data-length: 121088
727 finalized changelog write
728 persisting changelog nodemap
729 new data start at 121088
730 persisted changelog nodemap
731 docket-details:
732 uid: 43c37dde
733 actual-tip: 5006
734 tip-rev: 5006
735 data-length: 121280
736 $ sh "$RUNTESTDIR"/testlib/wait-on-file 10 sync-files/right-done
737 $ cat outputs/right.txt
738 nodemap-race: right side start of the locking sequence
739 nodemap-race: right side reading changelog
740 nodemap-race: right side reading of changelog is done
741 docket-details:
742 uid: 43c37dde
743 actual-tip: 5006
744 tip-rev: 5005
745 data-length: 121088
746 nodemap-race: right side ready to wait for the lock
747 nodemap-race: right side locked and ready to commit
748 docket-details:
749 uid: 43c37dde
750 actual-tip: 5006
751 tip-rev: 5005
752 data-length: 121088
753 right ready to write, waiting for reader
754 right proceeding with writing its changelog index and nodemap
755 finalized changelog write
756 persisting changelog nodemap
757 new data start at 121088
758 persisted changelog nodemap
759 docket-details:
760 uid: 43c37dde
761 actual-tip: 5007
762 tip-rev: 5007
763 data-length: 121472
764 $ sh "$RUNTESTDIR"/testlib/wait-on-file 10 sync-files/reader-done
765 $ cat outputs/reader.txt
766 reader: reading changelog
767 reader ready to read the changelog, waiting for right
768 reader: nodemap docket read
769 record-data-length: 121280
770 actual-data-length: 121280
771 file-actual-length: 121472
772 reader: changelog read
773 docket-details:
774 uid: 43c37dde
775 actual-tip: 5006
776 tip-rev: 5006
777 data-length: 121280
778 tip-rev: 5006
779 tip-node: 492901161367
780 node-rev: 5006
781 error while checking revision: 18 (known-bad-output !)
782 Inconsistency: Revision 5007 found in nodemap is not in revlog indexi (known-bad-output !)
783
784 $ hg -R ./race-repo log -G -r 'head()'
785 o changeset: 5007:ac4a2abde241
786 | tag: tip
787 ~ parent: 5001:16395c3cf7e2
788 user: test
789 date: Thu Jan 01 00:00:00 1970 +0000
790 summary: right-side-commit
791
792 @ changeset: 5006:492901161367
793 | user: test
794 ~ date: Thu Jan 01 00:00:00 1970 +0000
795 summary: left-side-commit
796
797 $ hg -R ./other-wc log -G -r 'head()'
798 @ changeset: 5007:ac4a2abde241
799 | tag: tip
800 ~ parent: 5001:16395c3cf7e2
801 user: test
802 date: Thu Jan 01 00:00:00 1970 +0000
803 summary: right-side-commit
804
805 o changeset: 5006:492901161367
806 | user: test
807 ~ date: Thu Jan 01 00:00:00 1970 +0000
808 summary: left-side-commit
809
810 #endif
811
616 812 Test upgrade / downgrade
617 813 ========================
618 814
815 $ cd ./test-repo/
816
619 817 downgrading
620 818
621 819 $ cat << EOF >> .hg/hgrc
622 820 > [format]
623 821 > use-persistent-nodemap=no
624 822 > EOF
625 823 $ hg debugformat -v
626 824 format-variant repo config default
627 825 fncache: yes yes yes
628 826 dirstate-v2: no no no
629 827 dotencode: yes yes yes
630 828 generaldelta: yes yes yes
631 829 share-safe: yes yes no
632 830 sparserevlog: yes yes yes
633 831 persistent-nodemap: yes no no
634 832 copies-sdc: no no no
635 833 revlog-v2: no no no
636 834 changelog-v2: no no no
637 835 plain-cl-delta: yes yes yes
638 836 compression: zlib zlib zlib (no-zstd !)
639 837 compression: zstd zstd zstd (zstd !)
640 838 compression-level: default default default
641 839 $ hg debugupgraderepo --run --no-backup --quiet
642 840 upgrade will perform the following actions:
643 841
644 842 requirements
645 843 preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, sparserevlog, store (no-zstd no-dirstate-v2 !)
646 844 preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, revlogv1, share-safe, sparserevlog, store (zstd no-dirstate-v2 !)
647 845 preserved: dotencode, exp-dirstate-v2, fncache, generaldelta, revlog-compression-zstd, revlogv1, share-safe, sparserevlog, store (zstd dirstate-v2 !)
648 846 removed: persistent-nodemap
649 847
650 848 processed revlogs:
651 849 - all-filelogs
652 850 - changelog
653 851 - manifest
654 852
655 853 $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
656 854 00changelog-*.nd (glob)
657 855 00manifest-*.nd (glob)
658 856 undo.backup.00changelog.n
659 857 undo.backup.00manifest.n
660 858 $ hg debugnodemap --metadata
661 859
662 860
663 861 upgrading
664 862
665 863 $ cat << EOF >> .hg/hgrc
666 864 > [format]
667 865 > use-persistent-nodemap=yes
668 866 > EOF
669 867 $ hg debugformat -v
670 868 format-variant repo config default
671 869 fncache: yes yes yes
672 870 dirstate-v2: no no no
673 871 dotencode: yes yes yes
674 872 generaldelta: yes yes yes
675 873 share-safe: yes yes no
676 874 sparserevlog: yes yes yes
677 875 persistent-nodemap: no yes no
678 876 copies-sdc: no no no
679 877 revlog-v2: no no no
680 878 changelog-v2: no no no
681 879 plain-cl-delta: yes yes yes
682 880 compression: zlib zlib zlib (no-zstd !)
683 881 compression: zstd zstd zstd (zstd !)
684 882 compression-level: default default default
685 883 $ hg debugupgraderepo --run --no-backup --quiet
686 884 upgrade will perform the following actions:
687 885
688 886 requirements
689 887 preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, sparserevlog, store (no-zstd no-dirstate-v2 !)
690 888 preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, revlogv1, share-safe, sparserevlog, store (zstd no-dirstate-v2 !)
691 889 preserved: dotencode, exp-dirstate-v2, fncache, generaldelta, revlog-compression-zstd, revlogv1, share-safe, sparserevlog, store (zstd dirstate-v2 !)
692 890 added: persistent-nodemap
693 891
694 892 processed revlogs:
695 893 - all-filelogs
696 894 - changelog
697 895 - manifest
698 896
699 897 $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
700 898 00changelog-*.nd (glob)
701 899 00changelog.n
702 900 00manifest-*.nd (glob)
703 901 00manifest.n
704 902 undo.backup.00changelog.n
705 903 undo.backup.00manifest.n
706 904
707 905 $ hg debugnodemap --metadata
708 906 uid: * (glob)
709 907 tip-rev: 5005
710 908 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
711 909 data-length: 121088
712 910 data-unused: 0
713 911 data-unused: 0.000%
714 912
715 913 Running unrelated upgrade
716 914
717 915 $ hg debugupgraderepo --run --no-backup --quiet --optimize re-delta-all
718 916 upgrade will perform the following actions:
719 917
720 918 requirements
721 919 preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, sparserevlog, store (no-zstd no-dirstate-v2 !)
722 920 preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlog-compression-zstd, revlogv1, share-safe, sparserevlog, store (zstd no-dirstate-v2 !)
723 921 preserved: dotencode, exp-dirstate-v2, fncache, generaldelta, persistent-nodemap, revlog-compression-zstd, revlogv1, share-safe, sparserevlog, store (zstd dirstate-v2 !)
724 922
725 923 optimisations: re-delta-all
726 924
727 925 processed revlogs:
728 926 - all-filelogs
729 927 - changelog
730 928 - manifest
731 929
732 930 $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
733 931 00changelog-*.nd (glob)
734 932 00changelog.n
735 933 00manifest-*.nd (glob)
736 934 00manifest.n
737 935
738 936 $ hg debugnodemap --metadata
739 937 uid: * (glob)
740 938 tip-rev: 5005
741 939 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
742 940 data-length: 121088
743 941 data-unused: 0
744 942 data-unused: 0.000%
745 943
746 944 Persistent nodemap and local/streaming clone
747 945 ============================================
748 946
749 947 $ cd ..
750 948
751 949 standard clone
752 950 --------------
753 951
754 952 The persistent nodemap should exist after a streaming clone
755 953
756 954 $ hg clone --pull --quiet -U test-repo standard-clone
757 955 $ ls -1 standard-clone/.hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
758 956 00changelog-*.nd (glob)
759 957 00changelog.n
760 958 00manifest-*.nd (glob)
761 959 00manifest.n
762 960 $ hg -R standard-clone debugnodemap --metadata
763 961 uid: * (glob)
764 962 tip-rev: 5005
765 963 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
766 964 data-length: 121088
767 965 data-unused: 0
768 966 data-unused: 0.000%
769 967
770 968
771 969 local clone
772 970 ------------
773 971
774 972 The persistent nodemap should exist after a streaming clone
775 973
776 974 $ hg clone -U test-repo local-clone
777 975 $ ls -1 local-clone/.hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
778 976 00changelog-*.nd (glob)
779 977 00changelog.n
780 978 00manifest-*.nd (glob)
781 979 00manifest.n
782 980 $ hg -R local-clone debugnodemap --metadata
783 981 uid: * (glob)
784 982 tip-rev: 5005
785 983 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
786 984 data-length: 121088
787 985 data-unused: 0
788 986 data-unused: 0.000%
789 987
790 988 Test various corruption case
791 989 ============================
792 990
793 991 Missing datafile
794 992 ----------------
795 993
796 994 Test behavior with a missing datafile
797 995
798 996 $ hg clone --quiet --pull test-repo corruption-test-repo
799 997 $ ls -1 corruption-test-repo/.hg/store/00changelog*
800 998 corruption-test-repo/.hg/store/00changelog-*.nd (glob)
801 999 corruption-test-repo/.hg/store/00changelog.d
802 1000 corruption-test-repo/.hg/store/00changelog.i
803 1001 corruption-test-repo/.hg/store/00changelog.n
804 1002 $ rm corruption-test-repo/.hg/store/00changelog*.nd
805 1003 $ hg log -R corruption-test-repo -r .
806 1004 changeset: 5005:90d5d3ba2fc4
807 1005 tag: tip
808 1006 user: test
809 1007 date: Thu Jan 01 00:00:00 1970 +0000
810 1008 summary: a2
811 1009
812 1010 $ ls -1 corruption-test-repo/.hg/store/00changelog*
813 1011 corruption-test-repo/.hg/store/00changelog.d
814 1012 corruption-test-repo/.hg/store/00changelog.i
815 1013 corruption-test-repo/.hg/store/00changelog.n
816 1014
817 1015 Truncated data file
818 1016 -------------------
819 1017
820 1018 Test behavior with a too short datafile
821 1019
822 1020 rebuild the missing data
823 1021 $ hg -R corruption-test-repo debugupdatecache
824 1022 $ ls -1 corruption-test-repo/.hg/store/00changelog*
825 1023 corruption-test-repo/.hg/store/00changelog-*.nd (glob)
826 1024 corruption-test-repo/.hg/store/00changelog.d
827 1025 corruption-test-repo/.hg/store/00changelog.i
828 1026 corruption-test-repo/.hg/store/00changelog.n
829 1027
830 1028 truncate the file
831 1029
832 1030 $ datafilepath=`ls corruption-test-repo/.hg/store/00changelog*.nd`
833 1031 $ f -s $datafilepath
834 1032 corruption-test-repo/.hg/store/00changelog-*.nd: size=121088 (glob)
835 1033 $ dd if=$datafilepath bs=1000 count=10 of=$datafilepath-tmp status=noxfer
836 1034 10+0 records in
837 1035 10+0 records out
838 1036 $ mv $datafilepath-tmp $datafilepath
839 1037 $ f -s $datafilepath
840 1038 corruption-test-repo/.hg/store/00changelog-*.nd: size=10000 (glob)
841 1039
842 1040 Check that Mercurial reaction to this event
843 1041
844 1042 $ hg -R corruption-test-repo log -r . --traceback
845 1043 changeset: 5005:90d5d3ba2fc4
846 1044 tag: tip
847 1045 user: test
848 1046 date: Thu Jan 01 00:00:00 1970 +0000
849 1047 summary: a2
850 1048
851 1049
852 1050
853 1051 stream clone
854 1052 ============
855 1053
856 1054 The persistent nodemap should exist after a streaming clone
857 1055
858 1056 Simple case
859 1057 -----------
860 1058
861 1059 No race condition
862 1060
863 1061 $ hg clone -U --stream --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/test-repo stream-clone --debug | egrep '00(changelog|manifest)'
864 1062 adding [s] 00manifest.n (62 bytes)
865 1063 adding [s] 00manifest-*.nd (118 KB) (glob)
866 1064 adding [s] 00changelog.n (62 bytes)
867 1065 adding [s] 00changelog-*.nd (118 KB) (glob)
868 1066 adding [s] 00manifest.d (452 KB) (no-zstd !)
869 1067 adding [s] 00manifest.d (491 KB) (zstd !)
870 1068 adding [s] 00changelog.d (360 KB) (no-zstd !)
871 1069 adding [s] 00changelog.d (368 KB) (zstd !)
872 1070 adding [s] 00manifest.i (313 KB)
873 1071 adding [s] 00changelog.i (313 KB)
874 1072 $ ls -1 stream-clone/.hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
875 1073 00changelog-*.nd (glob)
876 1074 00changelog.n
877 1075 00manifest-*.nd (glob)
878 1076 00manifest.n
879 1077 $ hg -R stream-clone debugnodemap --metadata
880 1078 uid: * (glob)
881 1079 tip-rev: 5005
882 1080 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
883 1081 data-length: 121088
884 1082 data-unused: 0
885 1083 data-unused: 0.000%
886 1084
887 1085 new data appened
888 1086 -----------------
889 1087
890 1088 Other commit happening on the server during the stream clone
891 1089
892 1090 setup the step-by-step stream cloning
893 1091
894 1092 $ HG_TEST_STREAM_WALKED_FILE_1="$TESTTMP/sync_file_walked_1"
895 1093 $ export HG_TEST_STREAM_WALKED_FILE_1
896 1094 $ HG_TEST_STREAM_WALKED_FILE_2="$TESTTMP/sync_file_walked_2"
897 1095 $ export HG_TEST_STREAM_WALKED_FILE_2
898 1096 $ HG_TEST_STREAM_WALKED_FILE_3="$TESTTMP/sync_file_walked_3"
899 1097 $ export HG_TEST_STREAM_WALKED_FILE_3
900 1098 $ cat << EOF >> test-repo/.hg/hgrc
901 1099 > [extensions]
902 1100 > steps=$RUNTESTDIR/testlib/ext-stream-clone-steps.py
903 1101 > EOF
904 1102
905 1103 Check and record file state beforehand
906 1104
907 1105 $ f --size test-repo/.hg/store/00changelog*
908 1106 test-repo/.hg/store/00changelog-*.nd: size=121088 (glob)
909 1107 test-repo/.hg/store/00changelog.d: size=376891 (zstd !)
910 1108 test-repo/.hg/store/00changelog.d: size=368890 (no-zstd !)
911 1109 test-repo/.hg/store/00changelog.i: size=320384
912 1110 test-repo/.hg/store/00changelog.n: size=62
913 1111 $ hg -R test-repo debugnodemap --metadata | tee server-metadata.txt
914 1112 uid: * (glob)
915 1113 tip-rev: 5005
916 1114 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
917 1115 data-length: 121088
918 1116 data-unused: 0
919 1117 data-unused: 0.000%
920 1118
921 1119 Prepare a commit
922 1120
923 1121 $ echo foo >> test-repo/foo
924 1122 $ hg -R test-repo/ add test-repo/foo
925 1123
926 1124 Do a mix of clone and commit at the same time so that the file listed on disk differ at actual transfer time.
927 1125
928 1126 $ (hg clone -U --stream --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/test-repo stream-clone-race-1 --debug 2>> clone-output | egrep '00(changelog|manifest)' >> clone-output; touch $HG_TEST_STREAM_WALKED_FILE_3) &
929 1127 $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_1
930 1128 $ hg -R test-repo/ commit -m foo
931 1129 $ touch $HG_TEST_STREAM_WALKED_FILE_2
932 1130 $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_3
933 1131 $ cat clone-output
934 1132 adding [s] 00manifest.n (62 bytes)
935 1133 adding [s] 00manifest-*.nd (118 KB) (glob)
936 1134 adding [s] 00changelog.n (62 bytes)
937 1135 adding [s] 00changelog-*.nd (118 KB) (glob)
938 1136 adding [s] 00manifest.d (452 KB) (no-zstd !)
939 1137 adding [s] 00manifest.d (491 KB) (zstd !)
940 1138 adding [s] 00changelog.d (360 KB) (no-zstd !)
941 1139 adding [s] 00changelog.d (368 KB) (zstd !)
942 1140 adding [s] 00manifest.i (313 KB)
943 1141 adding [s] 00changelog.i (313 KB)
944 1142
945 1143 Check the result state
946 1144
947 1145 $ f --size stream-clone-race-1/.hg/store/00changelog*
948 1146 stream-clone-race-1/.hg/store/00changelog-*.nd: size=121088 (glob)
949 1147 stream-clone-race-1/.hg/store/00changelog.d: size=368890 (no-zstd !)
950 1148 stream-clone-race-1/.hg/store/00changelog.d: size=376891 (zstd !)
951 1149 stream-clone-race-1/.hg/store/00changelog.i: size=320384
952 1150 stream-clone-race-1/.hg/store/00changelog.n: size=62
953 1151
954 1152 $ hg -R stream-clone-race-1 debugnodemap --metadata | tee client-metadata.txt
955 1153 uid: * (glob)
956 1154 tip-rev: 5005
957 1155 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
958 1156 data-length: 121088
959 1157 data-unused: 0
960 1158 data-unused: 0.000%
961 1159
962 1160 We get a usable nodemap, so no rewrite would be needed and the metadata should be identical
963 1161 (ie: the following diff should be empty)
964 1162
965 1163 This isn't the case for the `no-rust` `no-pure` implementation as it use a very minimal nodemap implementation that unconditionnaly rewrite the nodemap "all the time".
966 1164
967 1165 #if no-rust no-pure
968 1166 $ diff -u server-metadata.txt client-metadata.txt
969 1167 --- server-metadata.txt * (glob)
970 1168 +++ client-metadata.txt * (glob)
971 1169 @@ -1,4 +1,4 @@
972 1170 -uid: * (glob)
973 1171 +uid: * (glob)
974 1172 tip-rev: 5005
975 1173 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
976 1174 data-length: 121088
977 1175 [1]
978 1176 #else
979 1177 $ diff -u server-metadata.txt client-metadata.txt
980 1178 #endif
981 1179
982 1180
983 1181 Clean up after the test.
984 1182
985 1183 $ rm -f "$HG_TEST_STREAM_WALKED_FILE_1"
986 1184 $ rm -f "$HG_TEST_STREAM_WALKED_FILE_2"
987 1185 $ rm -f "$HG_TEST_STREAM_WALKED_FILE_3"
988 1186
989 1187 full regeneration
990 1188 -----------------
991 1189
992 1190 A full nodemap is generated
993 1191
994 1192 (ideally this test would append enough data to make sure the nodemap data file
995 1193 get changed, however to make thing simpler we will force the regeneration for
996 1194 this test.
997 1195
998 1196 Check the initial state
999 1197
1000 1198 $ f --size test-repo/.hg/store/00changelog*
1001 1199 test-repo/.hg/store/00changelog-*.nd: size=121344 (glob) (rust !)
1002 1200 test-repo/.hg/store/00changelog-*.nd: size=121344 (glob) (pure !)
1003 1201 test-repo/.hg/store/00changelog-*.nd: size=121152 (glob) (no-rust no-pure !)
1004 1202 test-repo/.hg/store/00changelog.d: size=376950 (zstd !)
1005 1203 test-repo/.hg/store/00changelog.d: size=368949 (no-zstd !)
1006 1204 test-repo/.hg/store/00changelog.i: size=320448
1007 1205 test-repo/.hg/store/00changelog.n: size=62
1008 1206 $ hg -R test-repo debugnodemap --metadata | tee server-metadata-2.txt
1009 1207 uid: * (glob)
1010 1208 tip-rev: 5006
1011 1209 tip-node: ed2ec1eef9aa2a0ec5057c51483bc148d03e810b
1012 1210 data-length: 121344 (rust !)
1013 1211 data-length: 121344 (pure !)
1014 1212 data-length: 121152 (no-rust no-pure !)
1015 1213 data-unused: 192 (rust !)
1016 1214 data-unused: 192 (pure !)
1017 1215 data-unused: 0 (no-rust no-pure !)
1018 1216 data-unused: 0.158% (rust !)
1019 1217 data-unused: 0.158% (pure !)
1020 1218 data-unused: 0.000% (no-rust no-pure !)
1021 1219
1022 1220 Performe the mix of clone and full refresh of the nodemap, so that the files
1023 1221 (and filenames) are different between listing time and actual transfer time.
1024 1222
1025 1223 $ (hg clone -U --stream --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/test-repo stream-clone-race-2 --debug 2>> clone-output-2 | egrep '00(changelog|manifest)' >> clone-output-2; touch $HG_TEST_STREAM_WALKED_FILE_3) &
1026 1224 $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_1
1027 1225 $ rm test-repo/.hg/store/00changelog.n
1028 1226 $ rm test-repo/.hg/store/00changelog-*.nd
1029 1227 $ hg -R test-repo/ debugupdatecache
1030 1228 $ touch $HG_TEST_STREAM_WALKED_FILE_2
1031 1229 $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_3
1032 1230
1033 1231 (note: the stream clone code wronly pick the `undo.` files)
1034 1232
1035 1233 $ cat clone-output-2
1036 1234 adding [s] undo.backup.00manifest.n (62 bytes) (known-bad-output !)
1037 1235 adding [s] undo.backup.00changelog.n (62 bytes) (known-bad-output !)
1038 1236 adding [s] 00manifest.n (62 bytes)
1039 1237 adding [s] 00manifest-*.nd (118 KB) (glob)
1040 1238 adding [s] 00changelog.n (62 bytes)
1041 1239 adding [s] 00changelog-*.nd (118 KB) (glob)
1042 1240 adding [s] 00manifest.d (492 KB) (zstd !)
1043 1241 adding [s] 00manifest.d (452 KB) (no-zstd !)
1044 1242 adding [s] 00changelog.d (360 KB) (no-zstd !)
1045 1243 adding [s] 00changelog.d (368 KB) (zstd !)
1046 1244 adding [s] 00manifest.i (313 KB)
1047 1245 adding [s] 00changelog.i (313 KB)
1048 1246
1049 1247 Check the result.
1050 1248
1051 1249 $ f --size stream-clone-race-2/.hg/store/00changelog*
1052 1250 stream-clone-race-2/.hg/store/00changelog-*.nd: size=121344 (glob) (rust !)
1053 1251 stream-clone-race-2/.hg/store/00changelog-*.nd: size=121344 (glob) (pure !)
1054 1252 stream-clone-race-2/.hg/store/00changelog-*.nd: size=121152 (glob) (no-rust no-pure !)
1055 1253 stream-clone-race-2/.hg/store/00changelog.d: size=376950 (zstd !)
1056 1254 stream-clone-race-2/.hg/store/00changelog.d: size=368949 (no-zstd !)
1057 1255 stream-clone-race-2/.hg/store/00changelog.i: size=320448
1058 1256 stream-clone-race-2/.hg/store/00changelog.n: size=62
1059 1257
1060 1258 $ hg -R stream-clone-race-2 debugnodemap --metadata | tee client-metadata-2.txt
1061 1259 uid: * (glob)
1062 1260 tip-rev: 5006
1063 1261 tip-node: ed2ec1eef9aa2a0ec5057c51483bc148d03e810b
1064 1262 data-length: 121344 (rust !)
1065 1263 data-unused: 192 (rust !)
1066 1264 data-unused: 0.158% (rust !)
1067 1265 data-length: 121152 (no-rust no-pure !)
1068 1266 data-unused: 0 (no-rust no-pure !)
1069 1267 data-unused: 0.000% (no-rust no-pure !)
1070 1268 data-length: 121344 (pure !)
1071 1269 data-unused: 192 (pure !)
1072 1270 data-unused: 0.158% (pure !)
1073 1271
1074 1272 We get a usable nodemap, so no rewrite would be needed and the metadata should be identical
1075 1273 (ie: the following diff should be empty)
1076 1274
1077 1275 This isn't the case for the `no-rust` `no-pure` implementation as it use a very minimal nodemap implementation that unconditionnaly rewrite the nodemap "all the time".
1078 1276
1079 1277 #if no-rust no-pure
1080 1278 $ diff -u server-metadata-2.txt client-metadata-2.txt
1081 1279 --- server-metadata-2.txt * (glob)
1082 1280 +++ client-metadata-2.txt * (glob)
1083 1281 @@ -1,4 +1,4 @@
1084 1282 -uid: * (glob)
1085 1283 +uid: * (glob)
1086 1284 tip-rev: 5006
1087 1285 tip-node: ed2ec1eef9aa2a0ec5057c51483bc148d03e810b
1088 1286 data-length: 121152
1089 1287 [1]
1090 1288 #else
1091 1289 $ diff -u server-metadata-2.txt client-metadata-2.txt
1092 1290 #endif
1093 1291
1094 1292 Clean up after the test
1095 1293
1096 1294 $ rm -f $HG_TEST_STREAM_WALKED_FILE_1
1097 1295 $ rm -f $HG_TEST_STREAM_WALKED_FILE_2
1098 1296 $ rm -f $HG_TEST_STREAM_WALKED_FILE_3
1099 1297
General Comments 0
You need to be logged in to leave comments. Login now