##// END OF EJS Templates
dirstate-map: add a missing debug wait point when accessing the v2 docket...
Raphaël Gomès -
r52946:af54626b default
parent child Browse files
Show More
@@ -1,880 +1,882
1 # dirstatemap.py
1 # dirstatemap.py
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 from __future__ import annotations
6 from __future__ import annotations
7
7
8 from typing import (
8 from typing import (
9 Optional,
9 Optional,
10 TYPE_CHECKING,
10 TYPE_CHECKING,
11 )
11 )
12
12
13 from .i18n import _
13 from .i18n import _
14
14
15 from . import (
15 from . import (
16 error,
16 error,
17 pathutil,
17 pathutil,
18 policy,
18 policy,
19 testing,
19 testing,
20 txnutil,
20 txnutil,
21 typelib,
21 typelib,
22 util,
22 util,
23 )
23 )
24
24
25 from .dirstateutils import (
25 from .dirstateutils import (
26 docket as docketmod,
26 docket as docketmod,
27 v2,
27 v2,
28 )
28 )
29
29
30 if TYPE_CHECKING:
30 if TYPE_CHECKING:
31 from . import (
31 from . import (
32 ui as uimod,
32 ui as uimod,
33 )
33 )
34
34
35 parsers = policy.importmod('parsers')
35 parsers = policy.importmod('parsers')
36 rustmod = policy.importrust('dirstate')
36 rustmod = policy.importrust('dirstate')
37
37
38 propertycache = util.propertycache
38 propertycache = util.propertycache
39
39
40 if rustmod is None:
40 if rustmod is None:
41 DirstateItem = parsers.DirstateItem
41 DirstateItem = parsers.DirstateItem
42 else:
42 else:
43 DirstateItem = rustmod.DirstateItem
43 DirstateItem = rustmod.DirstateItem
44
44
45 rangemask = 0x7FFFFFFF
45 rangemask = 0x7FFFFFFF
46
46
47 WRITE_MODE_AUTO = 0
47 WRITE_MODE_AUTO = 0
48 WRITE_MODE_FORCE_NEW = 1
48 WRITE_MODE_FORCE_NEW = 1
49 WRITE_MODE_FORCE_APPEND = 2
49 WRITE_MODE_FORCE_APPEND = 2
50
50
51
51
52 V2_MAX_READ_ATTEMPTS = 5
52 V2_MAX_READ_ATTEMPTS = 5
53
53
54
54
55 class _dirstatemapcommon:
55 class _dirstatemapcommon:
56 """
56 """
57 Methods that are identical for both implementations of the dirstatemap
57 Methods that are identical for both implementations of the dirstatemap
58 class, with and without Rust extensions enabled.
58 class, with and without Rust extensions enabled.
59 """
59 """
60
60
61 _use_dirstate_v2: bool
61 _use_dirstate_v2: bool
62 _nodeconstants: typelib.NodeConstants
62 _nodeconstants: typelib.NodeConstants
63 _ui: "uimod.ui"
63 _ui: "uimod.ui"
64 _root: bytes
64 _root: bytes
65 _filename: bytes
65 _filename: bytes
66 _nodelen: int
66 _nodelen: int
67 _dirtyparents: bool
67 _dirtyparents: bool
68 _docket: Optional["docketmod.DirstateDocket"]
68 _docket: Optional["docketmod.DirstateDocket"]
69 _write_mode: int
69 _write_mode: int
70 _pendingmode: Optional[bool]
70 _pendingmode: Optional[bool]
71 identity: Optional[typelib.CacheStat]
71 identity: Optional[typelib.CacheStat]
72
72
73 # please pytype
73 # please pytype
74
74
75 _map = None
75 _map = None
76 copymap = None
76 copymap = None
77
77
78 def __init__(
78 def __init__(
79 self,
79 self,
80 ui: "uimod.ui",
80 ui: "uimod.ui",
81 opener,
81 opener,
82 root: bytes,
82 root: bytes,
83 nodeconstants: typelib.NodeConstants,
83 nodeconstants: typelib.NodeConstants,
84 use_dirstate_v2: bool,
84 use_dirstate_v2: bool,
85 ) -> None:
85 ) -> None:
86 self._use_dirstate_v2 = use_dirstate_v2
86 self._use_dirstate_v2 = use_dirstate_v2
87 self._nodeconstants = nodeconstants
87 self._nodeconstants = nodeconstants
88 self._ui = ui
88 self._ui = ui
89 self._opener = opener
89 self._opener = opener
90 self._root = root
90 self._root = root
91 self._filename = b'dirstate'
91 self._filename = b'dirstate'
92 self._nodelen = 20 # Also update Rust code when changing this!
92 self._nodelen = 20 # Also update Rust code when changing this!
93 self._parents = None
93 self._parents = None
94 self._dirtyparents = False
94 self._dirtyparents = False
95 self._docket = None
95 self._docket = None
96 write_mode = ui.config(b"devel", b"dirstate.v2.data_update_mode")
96 write_mode = ui.config(b"devel", b"dirstate.v2.data_update_mode")
97 if write_mode == b"auto":
97 if write_mode == b"auto":
98 self._write_mode = WRITE_MODE_AUTO
98 self._write_mode = WRITE_MODE_AUTO
99 elif write_mode == b"force-append":
99 elif write_mode == b"force-append":
100 self._write_mode = WRITE_MODE_FORCE_APPEND
100 self._write_mode = WRITE_MODE_FORCE_APPEND
101 elif write_mode == b"force-new":
101 elif write_mode == b"force-new":
102 self._write_mode = WRITE_MODE_FORCE_NEW
102 self._write_mode = WRITE_MODE_FORCE_NEW
103 else:
103 else:
104 # unknown value, fallback to default
104 # unknown value, fallback to default
105 self._write_mode = WRITE_MODE_AUTO
105 self._write_mode = WRITE_MODE_AUTO
106
106
107 # for consistent view between _pl() and _read() invocations
107 # for consistent view between _pl() and _read() invocations
108 self._pendingmode = None
108 self._pendingmode = None
109
109
110 def _set_identity(self) -> None:
110 def _set_identity(self) -> None:
111 self.identity = self._get_current_identity()
111 self.identity = self._get_current_identity()
112
112
113 def _get_current_identity(self) -> Optional[typelib.CacheStat]:
113 def _get_current_identity(self) -> Optional[typelib.CacheStat]:
114 try:
114 try:
115 return util.cachestat(self._opener.join(self._filename))
115 return util.cachestat(self._opener.join(self._filename))
116 except FileNotFoundError:
116 except FileNotFoundError:
117 return None
117 return None
118
118
119 def may_need_refresh(self) -> bool:
119 def may_need_refresh(self) -> bool:
120 if 'identity' not in vars(self):
120 if 'identity' not in vars(self):
121 # no existing identity, we need a refresh
121 # no existing identity, we need a refresh
122 return True
122 return True
123 if self.identity is None:
123 if self.identity is None:
124 return True
124 return True
125 if not self.identity.cacheable():
125 if not self.identity.cacheable():
126 # We cannot trust the entry
126 # We cannot trust the entry
127 # XXX this is a problem on windows, NFS, or other inode less system
127 # XXX this is a problem on windows, NFS, or other inode less system
128 return True
128 return True
129 current_identity = self._get_current_identity()
129 current_identity = self._get_current_identity()
130 if current_identity is None:
130 if current_identity is None:
131 return True
131 return True
132 if not current_identity.cacheable():
132 if not current_identity.cacheable():
133 # We cannot trust the entry
133 # We cannot trust the entry
134 # XXX this is a problem on windows, NFS, or other inode less system
134 # XXX this is a problem on windows, NFS, or other inode less system
135 return True
135 return True
136 return current_identity != self.identity
136 return current_identity != self.identity
137
137
138 def preload(self) -> None:
138 def preload(self) -> None:
139 """Loads the underlying data, if it's not already loaded"""
139 """Loads the underlying data, if it's not already loaded"""
140 self._map
140 self._map
141
141
142 def get(self, key, default=None):
142 def get(self, key, default=None):
143 return self._map.get(key, default)
143 return self._map.get(key, default)
144
144
145 def __len__(self):
145 def __len__(self):
146 return len(self._map)
146 return len(self._map)
147
147
148 def __iter__(self):
148 def __iter__(self):
149 return iter(self._map)
149 return iter(self._map)
150
150
151 def __contains__(self, key):
151 def __contains__(self, key):
152 return key in self._map
152 return key in self._map
153
153
154 def __getitem__(self, item):
154 def __getitem__(self, item):
155 return self._map[item]
155 return self._map[item]
156
156
157 ### disk interaction
157 ### disk interaction
158
158
159 def _opendirstatefile(self):
159 def _opendirstatefile(self):
160 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
160 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
161 if self._pendingmode is not None and self._pendingmode != mode:
161 if self._pendingmode is not None and self._pendingmode != mode:
162 fp.close()
162 fp.close()
163 raise error.Abort(
163 raise error.Abort(
164 _(b'working directory state may be changed parallelly')
164 _(b'working directory state may be changed parallelly')
165 )
165 )
166 self._pendingmode = mode
166 self._pendingmode = mode
167 return fp
167 return fp
168
168
169 def _readdirstatefile(self, size: int = -1) -> bytes:
169 def _readdirstatefile(self, size: int = -1) -> bytes:
170 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
170 try:
171 try:
171 with self._opendirstatefile() as fp:
172 with self._opendirstatefile() as fp:
172 return fp.read(size)
173 return fp.read(size)
173 except FileNotFoundError:
174 except FileNotFoundError:
174 # File doesn't exist, so the current state is empty
175 # File doesn't exist, so the current state is empty
175 return b''
176 return b''
176
177
177 @property
178 @property
178 def docket(self) -> "docketmod.DirstateDocket":
179 def docket(self) -> "docketmod.DirstateDocket":
180 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
179 if not self._docket:
181 if not self._docket:
180 if not self._use_dirstate_v2:
182 if not self._use_dirstate_v2:
181 raise error.ProgrammingError(
183 raise error.ProgrammingError(
182 b'dirstate only has a docket in v2 format'
184 b'dirstate only has a docket in v2 format'
183 )
185 )
184 self._set_identity()
186 self._set_identity()
185 data = self._readdirstatefile()
187 data = self._readdirstatefile()
186 if data == b'' or data.startswith(docketmod.V2_FORMAT_MARKER):
188 if data == b'' or data.startswith(docketmod.V2_FORMAT_MARKER):
187 self._docket = docketmod.DirstateDocket.parse(
189 self._docket = docketmod.DirstateDocket.parse(
188 data, self._nodeconstants
190 data, self._nodeconstants
189 )
191 )
190 else:
192 else:
191 raise error.CorruptedDirstate(b"dirstate is not in v2 format")
193 raise error.CorruptedDirstate(b"dirstate is not in v2 format")
192 return self._docket
194 return self._docket
193
195
194 def _read_v2_data(self):
196 def _read_v2_data(self):
195 data = None
197 data = None
196 attempts = 0
198 attempts = 0
197 while attempts < V2_MAX_READ_ATTEMPTS:
199 while attempts < V2_MAX_READ_ATTEMPTS:
198 attempts += 1
200 attempts += 1
199 try:
201 try:
200 # TODO: use mmap when possible
202 # TODO: use mmap when possible
201 data = self._opener.read(self.docket.data_filename())
203 data = self._opener.read(self.docket.data_filename())
202 except FileNotFoundError:
204 except FileNotFoundError:
203 # read race detected between docket and data file
205 # read race detected between docket and data file
204 # reload the docket and retry
206 # reload the docket and retry
205 self._docket = None
207 self._docket = None
206 if data is None:
208 if data is None:
207 assert attempts >= V2_MAX_READ_ATTEMPTS
209 assert attempts >= V2_MAX_READ_ATTEMPTS
208 msg = b"dirstate read race happened %d times in a row"
210 msg = b"dirstate read race happened %d times in a row"
209 msg %= attempts
211 msg %= attempts
210 raise error.Abort(msg)
212 raise error.Abort(msg)
211 return self._opener.read(self.docket.data_filename())
213 return self._opener.read(self.docket.data_filename())
212
214
213 def write_v2_no_append(self, tr, st, meta, packed):
215 def write_v2_no_append(self, tr, st, meta, packed):
214 try:
216 try:
215 old_docket = self.docket
217 old_docket = self.docket
216 except error.CorruptedDirstate:
218 except error.CorruptedDirstate:
217 # This means we've identified a dirstate-v1 file on-disk when we
219 # This means we've identified a dirstate-v1 file on-disk when we
218 # were expecting a dirstate-v2 docket. We've managed to recover
220 # were expecting a dirstate-v2 docket. We've managed to recover
219 # from that unexpected situation, and now we want to write back a
221 # from that unexpected situation, and now we want to write back a
220 # dirstate-v2 file to make the on-disk situation right again.
222 # dirstate-v2 file to make the on-disk situation right again.
221 #
223 #
222 # This shouldn't be triggered since `self.docket` is cached and
224 # This shouldn't be triggered since `self.docket` is cached and
223 # we would have called parents() or read() first, but it's here
225 # we would have called parents() or read() first, but it's here
224 # just in case.
226 # just in case.
225 old_docket = None
227 old_docket = None
226
228
227 new_docket = docketmod.DirstateDocket.with_new_uuid(
229 new_docket = docketmod.DirstateDocket.with_new_uuid(
228 self.parents(), len(packed), meta
230 self.parents(), len(packed), meta
229 )
231 )
230 if old_docket is not None and old_docket.uuid == new_docket.uuid:
232 if old_docket is not None and old_docket.uuid == new_docket.uuid:
231 raise error.ProgrammingError(b'dirstate docket name collision')
233 raise error.ProgrammingError(b'dirstate docket name collision')
232 data_filename = new_docket.data_filename()
234 data_filename = new_docket.data_filename()
233 self._opener.write(data_filename, packed)
235 self._opener.write(data_filename, packed)
234 # tell the transaction that we are adding a new file
236 # tell the transaction that we are adding a new file
235 if tr is not None:
237 if tr is not None:
236 tr.addbackup(data_filename, location=b'plain')
238 tr.addbackup(data_filename, location=b'plain')
237 # Write the new docket after the new data file has been
239 # Write the new docket after the new data file has been
238 # written. Because `st` was opened with `atomictemp=True`,
240 # written. Because `st` was opened with `atomictemp=True`,
239 # the actual `.hg/dirstate` file is only affected on close.
241 # the actual `.hg/dirstate` file is only affected on close.
240 st.write(new_docket.serialize())
242 st.write(new_docket.serialize())
241 st.close()
243 st.close()
242 # Remove the old data file after the new docket pointing to
244 # Remove the old data file after the new docket pointing to
243 # the new data file was written.
245 # the new data file was written.
244 if old_docket is not None and old_docket.uuid:
246 if old_docket is not None and old_docket.uuid:
245 data_filename = old_docket.data_filename()
247 data_filename = old_docket.data_filename()
246 if tr is not None:
248 if tr is not None:
247 tr.addbackup(data_filename, location=b'plain')
249 tr.addbackup(data_filename, location=b'plain')
248 unlink = lambda _tr=None: self._opener.unlink(data_filename)
250 unlink = lambda _tr=None: self._opener.unlink(data_filename)
249 if tr:
251 if tr:
250 category = b"dirstate-v2-clean-" + old_docket.uuid
252 category = b"dirstate-v2-clean-" + old_docket.uuid
251 tr.addpostclose(category, unlink)
253 tr.addpostclose(category, unlink)
252 else:
254 else:
253 unlink()
255 unlink()
254 self._docket = new_docket
256 self._docket = new_docket
255
257
256 ### reading/setting parents
258 ### reading/setting parents
257
259
258 def parents(self):
260 def parents(self):
259 if not self._parents:
261 if not self._parents:
260 if self._use_dirstate_v2:
262 if self._use_dirstate_v2:
261 try:
263 try:
262 self.docket
264 self.docket
263 except error.CorruptedDirstate as e:
265 except error.CorruptedDirstate as e:
264 # fall back to dirstate-v1 if we fail to read v2
266 # fall back to dirstate-v1 if we fail to read v2
265 self._v1_parents(e)
267 self._v1_parents(e)
266 else:
268 else:
267 self._parents = self.docket.parents
269 self._parents = self.docket.parents
268 else:
270 else:
269 self._v1_parents()
271 self._v1_parents()
270
272
271 return self._parents
273 return self._parents
272
274
273 def _v1_parents(self, from_v2_exception=None):
275 def _v1_parents(self, from_v2_exception=None):
274 read_len = self._nodelen * 2
276 read_len = self._nodelen * 2
275 st = self._readdirstatefile(read_len)
277 st = self._readdirstatefile(read_len)
276 l = len(st)
278 l = len(st)
277 if l == read_len:
279 if l == read_len:
278 self._parents = (
280 self._parents = (
279 st[: self._nodelen],
281 st[: self._nodelen],
280 st[self._nodelen : 2 * self._nodelen],
282 st[self._nodelen : 2 * self._nodelen],
281 )
283 )
282 elif l == 0:
284 elif l == 0:
283 self._parents = (
285 self._parents = (
284 self._nodeconstants.nullid,
286 self._nodeconstants.nullid,
285 self._nodeconstants.nullid,
287 self._nodeconstants.nullid,
286 )
288 )
287 else:
289 else:
288 hint = None
290 hint = None
289 if from_v2_exception is not None:
291 if from_v2_exception is not None:
290 hint = _(b"falling back to dirstate-v1 from v2 also failed")
292 hint = _(b"falling back to dirstate-v1 from v2 also failed")
291 raise error.Abort(
293 raise error.Abort(
292 _(b'working directory state appears damaged!'), hint
294 _(b'working directory state appears damaged!'), hint
293 )
295 )
294
296
295
297
296 class dirstatemap(_dirstatemapcommon):
298 class dirstatemap(_dirstatemapcommon):
297 """Map encapsulating the dirstate's contents.
299 """Map encapsulating the dirstate's contents.
298
300
299 The dirstate contains the following state:
301 The dirstate contains the following state:
300
302
301 - `identity` is the identity of the dirstate file, which can be used to
303 - `identity` is the identity of the dirstate file, which can be used to
302 detect when changes have occurred to the dirstate file.
304 detect when changes have occurred to the dirstate file.
303
305
304 - `parents` is a pair containing the parents of the working copy. The
306 - `parents` is a pair containing the parents of the working copy. The
305 parents are updated by calling `setparents`.
307 parents are updated by calling `setparents`.
306
308
307 - the state map maps filenames to tuples of (state, mode, size, mtime),
309 - the state map maps filenames to tuples of (state, mode, size, mtime),
308 where state is a single character representing 'normal', 'added',
310 where state is a single character representing 'normal', 'added',
309 'removed', or 'merged'. It is read by treating the dirstate as a
311 'removed', or 'merged'. It is read by treating the dirstate as a
310 dict. File state is updated by calling various methods (see each
312 dict. File state is updated by calling various methods (see each
311 documentation for details):
313 documentation for details):
312
314
313 - `reset_state`,
315 - `reset_state`,
314 - `set_tracked`
316 - `set_tracked`
315 - `set_untracked`
317 - `set_untracked`
316 - `set_clean`
318 - `set_clean`
317 - `set_possibly_dirty`
319 - `set_possibly_dirty`
318
320
319 - `copymap` maps destination filenames to their source filename.
321 - `copymap` maps destination filenames to their source filename.
320
322
321 The dirstate also provides the following views onto the state:
323 The dirstate also provides the following views onto the state:
322
324
323 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
325 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
324 form that they appear as in the dirstate.
326 form that they appear as in the dirstate.
325
327
326 - `dirfoldmap` is a dict mapping normalized directory names to the
328 - `dirfoldmap` is a dict mapping normalized directory names to the
327 denormalized form that they appear as in the dirstate.
329 denormalized form that they appear as in the dirstate.
328 """
330 """
329
331
330 ### Core data storage and access
332 ### Core data storage and access
331
333
332 @propertycache
334 @propertycache
333 def _map(self):
335 def _map(self):
334 self._map = {}
336 self._map = {}
335 self.read()
337 self.read()
336 return self._map
338 return self._map
337
339
338 @propertycache
340 @propertycache
339 def copymap(self):
341 def copymap(self):
340 self.copymap = {}
342 self.copymap = {}
341 self._map
343 self._map
342 return self.copymap
344 return self.copymap
343
345
344 def clear(self):
346 def clear(self):
345 self._map.clear()
347 self._map.clear()
346 self.copymap.clear()
348 self.copymap.clear()
347 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
349 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
348 util.clearcachedproperty(self, b"_dirs")
350 util.clearcachedproperty(self, b"_dirs")
349 util.clearcachedproperty(self, b"_alldirs")
351 util.clearcachedproperty(self, b"_alldirs")
350 util.clearcachedproperty(self, b"filefoldmap")
352 util.clearcachedproperty(self, b"filefoldmap")
351 util.clearcachedproperty(self, b"dirfoldmap")
353 util.clearcachedproperty(self, b"dirfoldmap")
352
354
353 def items(self):
355 def items(self):
354 return self._map.items()
356 return self._map.items()
355
357
356 # forward for python2,3 compat
358 # forward for python2,3 compat
357 iteritems = items
359 iteritems = items
358
360
359 def debug_iter(self, all):
361 def debug_iter(self, all):
360 """
362 """
361 Return an iterator of (filename, state, mode, size, mtime) tuples
363 Return an iterator of (filename, state, mode, size, mtime) tuples
362
364
363 `all` is unused when Rust is not enabled
365 `all` is unused when Rust is not enabled
364 """
366 """
365 for filename, item in self.items():
367 for filename, item in self.items():
366 yield (filename, item.state, item.mode, item.size, item.mtime)
368 yield (filename, item.state, item.mode, item.size, item.mtime)
367
369
368 def keys(self):
370 def keys(self):
369 return self._map.keys()
371 return self._map.keys()
370
372
371 ### reading/setting parents
373 ### reading/setting parents
372
374
373 def setparents(self, p1, p2, fold_p2=False):
375 def setparents(self, p1, p2, fold_p2=False):
374 self._parents = (p1, p2)
376 self._parents = (p1, p2)
375 self._dirtyparents = True
377 self._dirtyparents = True
376 copies = {}
378 copies = {}
377 if fold_p2:
379 if fold_p2:
378 for f, s in self._map.items():
380 for f, s in self._map.items():
379 # Discard "merged" markers when moving away from a merge state
381 # Discard "merged" markers when moving away from a merge state
380 if s.p2_info:
382 if s.p2_info:
381 source = self.copymap.pop(f, None)
383 source = self.copymap.pop(f, None)
382 if source:
384 if source:
383 copies[f] = source
385 copies[f] = source
384 s.drop_merge_data()
386 s.drop_merge_data()
385 return copies
387 return copies
386
388
387 ### disk interaction
389 ### disk interaction
388
390
389 def read(self):
391 def read(self):
390 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
392 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
391 if self._use_dirstate_v2:
393 if self._use_dirstate_v2:
392 try:
394 try:
393 self.docket
395 self.docket
394 except error.CorruptedDirstate:
396 except error.CorruptedDirstate:
395 # fall back to dirstate-v1 if we fail to read v2
397 # fall back to dirstate-v1 if we fail to read v2
396 self._set_identity()
398 self._set_identity()
397 st = self._readdirstatefile()
399 st = self._readdirstatefile()
398 else:
400 else:
399 if not self.docket.uuid:
401 if not self.docket.uuid:
400 return
402 return
401 testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
403 testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
402 st = self._read_v2_data()
404 st = self._read_v2_data()
403 else:
405 else:
404 self._set_identity()
406 self._set_identity()
405 st = self._readdirstatefile()
407 st = self._readdirstatefile()
406
408
407 if not st:
409 if not st:
408 return
410 return
409
411
410 # TODO: adjust this estimate for dirstate-v2
412 # TODO: adjust this estimate for dirstate-v2
411 if hasattr(parsers, 'dict_new_presized'):
413 if hasattr(parsers, 'dict_new_presized'):
412 # Make an estimate of the number of files in the dirstate based on
414 # Make an estimate of the number of files in the dirstate based on
413 # its size. This trades wasting some memory for avoiding costly
415 # its size. This trades wasting some memory for avoiding costly
414 # resizes. Each entry have a prefix of 17 bytes followed by one or
416 # resizes. Each entry have a prefix of 17 bytes followed by one or
415 # two path names. Studies on various large-scale real-world repositories
417 # two path names. Studies on various large-scale real-world repositories
416 # found 54 bytes a reasonable upper limit for the average path names.
418 # found 54 bytes a reasonable upper limit for the average path names.
417 # Copy entries are ignored for the sake of this estimate.
419 # Copy entries are ignored for the sake of this estimate.
418 self._map = parsers.dict_new_presized(len(st) // 71)
420 self._map = parsers.dict_new_presized(len(st) // 71)
419
421
420 # Python's garbage collector triggers a GC each time a certain number
422 # Python's garbage collector triggers a GC each time a certain number
421 # of container objects (the number being defined by
423 # of container objects (the number being defined by
422 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
424 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
423 # for each file in the dirstate. The C version then immediately marks
425 # for each file in the dirstate. The C version then immediately marks
424 # them as not to be tracked by the collector. However, this has no
426 # them as not to be tracked by the collector. However, this has no
425 # effect on when GCs are triggered, only on what objects the GC looks
427 # effect on when GCs are triggered, only on what objects the GC looks
426 # into. This means that O(number of files) GCs are unavoidable.
428 # into. This means that O(number of files) GCs are unavoidable.
427 # Depending on when in the process's lifetime the dirstate is parsed,
429 # Depending on when in the process's lifetime the dirstate is parsed,
428 # this can get very expensive. As a workaround, disable GC while
430 # this can get very expensive. As a workaround, disable GC while
429 # parsing the dirstate.
431 # parsing the dirstate.
430 #
432 #
431 # (we cannot decorate the function directly since it is in a C module)
433 # (we cannot decorate the function directly since it is in a C module)
432 if self._use_dirstate_v2:
434 if self._use_dirstate_v2:
433 try:
435 try:
434 self.docket
436 self.docket
435 except error.CorruptedDirstate:
437 except error.CorruptedDirstate:
436 # fall back to dirstate-v1 if we fail to parse v2
438 # fall back to dirstate-v1 if we fail to parse v2
437 parse_dirstate = util.nogc(parsers.parse_dirstate)
439 parse_dirstate = util.nogc(parsers.parse_dirstate)
438 p = parse_dirstate(self._map, self.copymap, st)
440 p = parse_dirstate(self._map, self.copymap, st)
439 else:
441 else:
440 p = self.docket.parents
442 p = self.docket.parents
441 meta = self.docket.tree_metadata
443 meta = self.docket.tree_metadata
442 parse_dirstate = util.nogc(v2.parse_dirstate)
444 parse_dirstate = util.nogc(v2.parse_dirstate)
443 parse_dirstate(self._map, self.copymap, st, meta)
445 parse_dirstate(self._map, self.copymap, st, meta)
444 else:
446 else:
445 parse_dirstate = util.nogc(parsers.parse_dirstate)
447 parse_dirstate = util.nogc(parsers.parse_dirstate)
446 p = parse_dirstate(self._map, self.copymap, st)
448 p = parse_dirstate(self._map, self.copymap, st)
447 if not self._dirtyparents:
449 if not self._dirtyparents:
448 self.setparents(*p)
450 self.setparents(*p)
449
451
450 # Avoid excess attribute lookups by fast pathing certain checks
452 # Avoid excess attribute lookups by fast pathing certain checks
451 self.__contains__ = self._map.__contains__
453 self.__contains__ = self._map.__contains__
452 self.__getitem__ = self._map.__getitem__
454 self.__getitem__ = self._map.__getitem__
453 self.get = self._map.get
455 self.get = self._map.get
454
456
455 def write(self, tr, st):
457 def write(self, tr, st):
456 if self._use_dirstate_v2:
458 if self._use_dirstate_v2:
457 packed, meta = v2.pack_dirstate(self._map, self.copymap)
459 packed, meta = v2.pack_dirstate(self._map, self.copymap)
458 self.write_v2_no_append(tr, st, meta, packed)
460 self.write_v2_no_append(tr, st, meta, packed)
459 else:
461 else:
460 packed = parsers.pack_dirstate(
462 packed = parsers.pack_dirstate(
461 self._map, self.copymap, self.parents()
463 self._map, self.copymap, self.parents()
462 )
464 )
463 st.write(packed)
465 st.write(packed)
464 st.close()
466 st.close()
465 self._dirtyparents = False
467 self._dirtyparents = False
466
468
467 @propertycache
469 @propertycache
468 def identity(self):
470 def identity(self):
469 self._map
471 self._map
470 return self.identity
472 return self.identity
471
473
472 ### code related to maintaining and accessing "extra" property
474 ### code related to maintaining and accessing "extra" property
473 # (e.g. "has_dir")
475 # (e.g. "has_dir")
474
476
475 def _dirs_incr(self, filename, old_entry=None):
477 def _dirs_incr(self, filename, old_entry=None):
476 """increment the dirstate counter if applicable"""
478 """increment the dirstate counter if applicable"""
477 if (
479 if (
478 old_entry is None or old_entry.removed
480 old_entry is None or old_entry.removed
479 ) and "_dirs" in self.__dict__:
481 ) and "_dirs" in self.__dict__:
480 self._dirs.addpath(filename)
482 self._dirs.addpath(filename)
481 if old_entry is None and "_alldirs" in self.__dict__:
483 if old_entry is None and "_alldirs" in self.__dict__:
482 self._alldirs.addpath(filename)
484 self._alldirs.addpath(filename)
483
485
484 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
486 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
485 """decrement the dirstate counter if applicable"""
487 """decrement the dirstate counter if applicable"""
486 if old_entry is not None:
488 if old_entry is not None:
487 if "_dirs" in self.__dict__ and not old_entry.removed:
489 if "_dirs" in self.__dict__ and not old_entry.removed:
488 self._dirs.delpath(filename)
490 self._dirs.delpath(filename)
489 if "_alldirs" in self.__dict__ and not remove_variant:
491 if "_alldirs" in self.__dict__ and not remove_variant:
490 self._alldirs.delpath(filename)
492 self._alldirs.delpath(filename)
491 elif remove_variant and "_alldirs" in self.__dict__:
493 elif remove_variant and "_alldirs" in self.__dict__:
492 self._alldirs.addpath(filename)
494 self._alldirs.addpath(filename)
493 if "filefoldmap" in self.__dict__:
495 if "filefoldmap" in self.__dict__:
494 normed = util.normcase(filename)
496 normed = util.normcase(filename)
495 self.filefoldmap.pop(normed, None)
497 self.filefoldmap.pop(normed, None)
496
498
497 @propertycache
499 @propertycache
498 def filefoldmap(self):
500 def filefoldmap(self):
499 """Returns a dictionary mapping normalized case paths to their
501 """Returns a dictionary mapping normalized case paths to their
500 non-normalized versions.
502 non-normalized versions.
501 """
503 """
502 try:
504 try:
503 makefilefoldmap = parsers.make_file_foldmap
505 makefilefoldmap = parsers.make_file_foldmap
504 except AttributeError:
506 except AttributeError:
505 pass
507 pass
506 else:
508 else:
507 return makefilefoldmap(
509 return makefilefoldmap(
508 self._map, util.normcasespec, util.normcasefallback
510 self._map, util.normcasespec, util.normcasefallback
509 )
511 )
510
512
511 f = {}
513 f = {}
512 normcase = util.normcase
514 normcase = util.normcase
513 for name, s in self._map.items():
515 for name, s in self._map.items():
514 if not s.removed:
516 if not s.removed:
515 f[normcase(name)] = name
517 f[normcase(name)] = name
516 f[b'.'] = b'.' # prevents useless util.fspath() invocation
518 f[b'.'] = b'.' # prevents useless util.fspath() invocation
517 return f
519 return f
518
520
519 @propertycache
521 @propertycache
520 def dirfoldmap(self):
522 def dirfoldmap(self):
521 f = {}
523 f = {}
522 normcase = util.normcase
524 normcase = util.normcase
523 for name in self._dirs:
525 for name in self._dirs:
524 f[normcase(name)] = name
526 f[normcase(name)] = name
525 return f
527 return f
526
528
527 def hastrackeddir(self, d):
529 def hastrackeddir(self, d):
528 """
530 """
529 Returns True if the dirstate contains a tracked (not removed) file
531 Returns True if the dirstate contains a tracked (not removed) file
530 in this directory.
532 in this directory.
531 """
533 """
532 return d in self._dirs
534 return d in self._dirs
533
535
534 def hasdir(self, d):
536 def hasdir(self, d):
535 """
537 """
536 Returns True if the dirstate contains a file (tracked or removed)
538 Returns True if the dirstate contains a file (tracked or removed)
537 in this directory.
539 in this directory.
538 """
540 """
539 return d in self._alldirs
541 return d in self._alldirs
540
542
541 @propertycache
543 @propertycache
542 def _dirs(self):
544 def _dirs(self):
543 return pathutil.dirs(self._map, only_tracked=True)
545 return pathutil.dirs(self._map, only_tracked=True)
544
546
545 @propertycache
547 @propertycache
546 def _alldirs(self):
548 def _alldirs(self):
547 return pathutil.dirs(self._map)
549 return pathutil.dirs(self._map)
548
550
549 ### code related to manipulation of entries and copy-sources
551 ### code related to manipulation of entries and copy-sources
550
552
551 def reset_state(
553 def reset_state(
552 self,
554 self,
553 filename,
555 filename,
554 wc_tracked=False,
556 wc_tracked=False,
555 p1_tracked=False,
557 p1_tracked=False,
556 p2_info=False,
558 p2_info=False,
557 has_meaningful_mtime=True,
559 has_meaningful_mtime=True,
558 parentfiledata=None,
560 parentfiledata=None,
559 ):
561 ):
560 """Set a entry to a given state, diregarding all previous state
562 """Set a entry to a given state, diregarding all previous state
561
563
562 This is to be used by the part of the dirstate API dedicated to
564 This is to be used by the part of the dirstate API dedicated to
563 adjusting the dirstate after a update/merge.
565 adjusting the dirstate after a update/merge.
564
566
565 note: calling this might result to no entry existing at all if the
567 note: calling this might result to no entry existing at all if the
566 dirstate map does not see any point at having one for this file
568 dirstate map does not see any point at having one for this file
567 anymore.
569 anymore.
568 """
570 """
569 # copy information are now outdated
571 # copy information are now outdated
570 # (maybe new information should be in directly passed to this function)
572 # (maybe new information should be in directly passed to this function)
571 self.copymap.pop(filename, None)
573 self.copymap.pop(filename, None)
572
574
573 if not (p1_tracked or p2_info or wc_tracked):
575 if not (p1_tracked or p2_info or wc_tracked):
574 old_entry = self._map.get(filename)
576 old_entry = self._map.get(filename)
575 self._drop_entry(filename)
577 self._drop_entry(filename)
576 self._dirs_decr(filename, old_entry=old_entry)
578 self._dirs_decr(filename, old_entry=old_entry)
577 return
579 return
578
580
579 old_entry = self._map.get(filename)
581 old_entry = self._map.get(filename)
580 self._dirs_incr(filename, old_entry)
582 self._dirs_incr(filename, old_entry)
581 entry = DirstateItem(
583 entry = DirstateItem(
582 wc_tracked=wc_tracked,
584 wc_tracked=wc_tracked,
583 p1_tracked=p1_tracked,
585 p1_tracked=p1_tracked,
584 p2_info=p2_info,
586 p2_info=p2_info,
585 has_meaningful_mtime=has_meaningful_mtime,
587 has_meaningful_mtime=has_meaningful_mtime,
586 parentfiledata=parentfiledata,
588 parentfiledata=parentfiledata,
587 )
589 )
588 self._map[filename] = entry
590 self._map[filename] = entry
589
591
590 def set_tracked(self, filename):
592 def set_tracked(self, filename):
591 new = False
593 new = False
592 entry = self.get(filename)
594 entry = self.get(filename)
593 if entry is None:
595 if entry is None:
594 self._dirs_incr(filename)
596 self._dirs_incr(filename)
595 entry = DirstateItem(
597 entry = DirstateItem(
596 wc_tracked=True,
598 wc_tracked=True,
597 )
599 )
598
600
599 self._map[filename] = entry
601 self._map[filename] = entry
600 new = True
602 new = True
601 elif not entry.tracked:
603 elif not entry.tracked:
602 self._dirs_incr(filename, entry)
604 self._dirs_incr(filename, entry)
603 entry.set_tracked()
605 entry.set_tracked()
604 self._refresh_entry(filename, entry)
606 self._refresh_entry(filename, entry)
605 new = True
607 new = True
606 else:
608 else:
607 # XXX This is probably overkill for more case, but we need this to
609 # XXX This is probably overkill for more case, but we need this to
608 # fully replace the `normallookup` call with `set_tracked` one.
610 # fully replace the `normallookup` call with `set_tracked` one.
609 # Consider smoothing this in the future.
611 # Consider smoothing this in the future.
610 entry.set_possibly_dirty()
612 entry.set_possibly_dirty()
611 self._refresh_entry(filename, entry)
613 self._refresh_entry(filename, entry)
612 return new
614 return new
613
615
614 def set_untracked(self, f):
616 def set_untracked(self, f):
615 """Mark a file as no longer tracked in the dirstate map"""
617 """Mark a file as no longer tracked in the dirstate map"""
616 entry = self.get(f)
618 entry = self.get(f)
617 if entry is None:
619 if entry is None:
618 return False
620 return False
619 else:
621 else:
620 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
622 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
621 if not entry.p2_info:
623 if not entry.p2_info:
622 self.copymap.pop(f, None)
624 self.copymap.pop(f, None)
623 entry.set_untracked()
625 entry.set_untracked()
624 self._refresh_entry(f, entry)
626 self._refresh_entry(f, entry)
625 return True
627 return True
626
628
627 def set_clean(self, filename, mode, size, mtime):
629 def set_clean(self, filename, mode, size, mtime):
628 """mark a file as back to a clean state"""
630 """mark a file as back to a clean state"""
629 entry = self[filename]
631 entry = self[filename]
630 size = size & rangemask
632 size = size & rangemask
631 entry.set_clean(mode, size, mtime)
633 entry.set_clean(mode, size, mtime)
632 self._refresh_entry(filename, entry)
634 self._refresh_entry(filename, entry)
633 self.copymap.pop(filename, None)
635 self.copymap.pop(filename, None)
634
636
635 def set_possibly_dirty(self, filename):
637 def set_possibly_dirty(self, filename):
636 """record that the current state of the file on disk is unknown"""
638 """record that the current state of the file on disk is unknown"""
637 entry = self[filename]
639 entry = self[filename]
638 entry.set_possibly_dirty()
640 entry.set_possibly_dirty()
639 self._refresh_entry(filename, entry)
641 self._refresh_entry(filename, entry)
640
642
641 def _refresh_entry(self, f, entry):
643 def _refresh_entry(self, f, entry):
642 """record updated state of an entry"""
644 """record updated state of an entry"""
643 if not entry.any_tracked:
645 if not entry.any_tracked:
644 self._map.pop(f, None)
646 self._map.pop(f, None)
645
647
646 def _drop_entry(self, f):
648 def _drop_entry(self, f):
647 """remove any entry for file f
649 """remove any entry for file f
648
650
649 This should also drop associated copy information
651 This should also drop associated copy information
650
652
651 The fact we actually need to drop it is the responsability of the caller
653 The fact we actually need to drop it is the responsability of the caller
652 """
654 """
653 self._map.pop(f, None)
655 self._map.pop(f, None)
654 self.copymap.pop(f, None)
656 self.copymap.pop(f, None)
655
657
656
658
657 if rustmod is not None:
659 if rustmod is not None:
658
660
659 class dirstatemap(_dirstatemapcommon):
661 class dirstatemap(_dirstatemapcommon):
660 ### Core data storage and access
662 ### Core data storage and access
661
663
662 @propertycache
664 @propertycache
663 def _map(self):
665 def _map(self):
664 """
666 """
665 Fills the Dirstatemap when called.
667 Fills the Dirstatemap when called.
666 """
668 """
667 # ignore HG_PENDING because identity is used only for writing
669 # ignore HG_PENDING because identity is used only for writing
668 self._set_identity()
670 self._set_identity()
669
671
670 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
672 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
671 if self._use_dirstate_v2:
673 if self._use_dirstate_v2:
672 try:
674 try:
673 self.docket
675 self.docket
674 except error.CorruptedDirstate as e:
676 except error.CorruptedDirstate as e:
675 # fall back to dirstate-v1 if we fail to read v2
677 # fall back to dirstate-v1 if we fail to read v2
676 parents = self._v1_map(e)
678 parents = self._v1_map(e)
677 else:
679 else:
678 parents = self.docket.parents
680 parents = self.docket.parents
679 inode = (
681 inode = (
680 self.identity.stat.st_ino
682 self.identity.stat.st_ino
681 if self.identity is not None
683 if self.identity is not None
682 and self.identity.stat is not None
684 and self.identity.stat is not None
683 else None
685 else None
684 )
686 )
685 testing.wait_on_cfg(
687 testing.wait_on_cfg(
686 self._ui, b'dirstate.post-docket-read-file'
688 self._ui, b'dirstate.post-docket-read-file'
687 )
689 )
688 if not self.docket.uuid:
690 if not self.docket.uuid:
689 data = b''
691 data = b''
690 self._map = rustmod.DirstateMap.new_empty()
692 self._map = rustmod.DirstateMap.new_empty()
691 else:
693 else:
692 data = self._read_v2_data()
694 data = self._read_v2_data()
693 self._map = rustmod.DirstateMap.new_v2(
695 self._map = rustmod.DirstateMap.new_v2(
694 data,
696 data,
695 self.docket.data_size,
697 self.docket.data_size,
696 self.docket.tree_metadata,
698 self.docket.tree_metadata,
697 self.docket.uuid,
699 self.docket.uuid,
698 inode,
700 inode,
699 )
701 )
700 parents = self.docket.parents
702 parents = self.docket.parents
701 else:
703 else:
702 parents = self._v1_map()
704 parents = self._v1_map()
703
705
704 if parents and not self._dirtyparents:
706 if parents and not self._dirtyparents:
705 self.setparents(*parents)
707 self.setparents(*parents)
706
708
707 self.__contains__ = self._map.__contains__
709 self.__contains__ = self._map.__contains__
708 self.__getitem__ = self._map.__getitem__
710 self.__getitem__ = self._map.__getitem__
709 self.get = self._map.get
711 self.get = self._map.get
710 return self._map
712 return self._map
711
713
712 def _v1_map(self, from_v2_exception=None):
714 def _v1_map(self, from_v2_exception=None):
713 self._set_identity()
715 self._set_identity()
714 inode = (
716 inode = (
715 self.identity.stat.st_ino
717 self.identity.stat.st_ino
716 if self.identity is not None and self.identity.stat is not None
718 if self.identity is not None and self.identity.stat is not None
717 else None
719 else None
718 )
720 )
719 try:
721 try:
720 self._map, parents = rustmod.DirstateMap.new_v1(
722 self._map, parents = rustmod.DirstateMap.new_v1(
721 self._readdirstatefile(), inode
723 self._readdirstatefile(), inode
722 )
724 )
723 except OSError as e:
725 except OSError as e:
724 if from_v2_exception is not None:
726 if from_v2_exception is not None:
725 raise e from from_v2_exception
727 raise e from from_v2_exception
726 raise
728 raise
727 return parents
729 return parents
728
730
729 @property
731 @property
730 def copymap(self):
732 def copymap(self):
731 return self._map.copymap()
733 return self._map.copymap()
732
734
733 def debug_iter(self, all):
735 def debug_iter(self, all):
734 """
736 """
735 Return an iterator of (filename, state, mode, size, mtime) tuples
737 Return an iterator of (filename, state, mode, size, mtime) tuples
736
738
737 `all`: also include with `state == b' '` dirstate tree nodes that
739 `all`: also include with `state == b' '` dirstate tree nodes that
738 don't have an associated `DirstateItem`.
740 don't have an associated `DirstateItem`.
739
741
740 """
742 """
741 return self._map.debug_iter(all)
743 return self._map.debug_iter(all)
742
744
743 def clear(self):
745 def clear(self):
744 self._map.clear()
746 self._map.clear()
745 self.setparents(
747 self.setparents(
746 self._nodeconstants.nullid, self._nodeconstants.nullid
748 self._nodeconstants.nullid, self._nodeconstants.nullid
747 )
749 )
748 util.clearcachedproperty(self, b"_dirs")
750 util.clearcachedproperty(self, b"_dirs")
749 util.clearcachedproperty(self, b"_alldirs")
751 util.clearcachedproperty(self, b"_alldirs")
750 util.clearcachedproperty(self, b"dirfoldmap")
752 util.clearcachedproperty(self, b"dirfoldmap")
751
753
752 def items(self):
754 def items(self):
753 return self._map.items()
755 return self._map.items()
754
756
755 # forward for python2,3 compat
757 # forward for python2,3 compat
756 iteritems = items
758 iteritems = items
757
759
758 def keys(self):
760 def keys(self):
759 return iter(self._map)
761 return iter(self._map)
760
762
761 ### reading/setting parents
763 ### reading/setting parents
762
764
763 def setparents(self, p1, p2, fold_p2=False):
765 def setparents(self, p1, p2, fold_p2=False):
764 self._parents = (p1, p2)
766 self._parents = (p1, p2)
765 self._dirtyparents = True
767 self._dirtyparents = True
766 copies = {}
768 copies = {}
767 if fold_p2:
769 if fold_p2:
768 copies = self._map.setparents_fixup()
770 copies = self._map.setparents_fixup()
769 return copies
771 return copies
770
772
771 ### disk interaction
773 ### disk interaction
772
774
773 @propertycache
775 @propertycache
774 def identity(self):
776 def identity(self):
775 self._map
777 self._map
776 return self.identity
778 return self.identity
777
779
778 def write(self, tr, st):
780 def write(self, tr, st):
779 if not self._use_dirstate_v2:
781 if not self._use_dirstate_v2:
780 p1, p2 = self.parents()
782 p1, p2 = self.parents()
781 packed = self._map.write_v1(p1, p2)
783 packed = self._map.write_v1(p1, p2)
782 st.write(packed)
784 st.write(packed)
783 st.close()
785 st.close()
784 self._dirtyparents = False
786 self._dirtyparents = False
785 return
787 return
786
788
787 write_mode = self._write_mode
789 write_mode = self._write_mode
788 try:
790 try:
789 docket = self.docket
791 docket = self.docket
790 except error.CorruptedDirstate:
792 except error.CorruptedDirstate:
791 # fall back to dirstate-v1 if we fail to parse v2
793 # fall back to dirstate-v1 if we fail to parse v2
792 docket = None
794 docket = None
793
795
794 # We can only append to an existing data file if there is one
796 # We can only append to an existing data file if there is one
795 if docket is None or docket.uuid is None:
797 if docket is None or docket.uuid is None:
796 write_mode = WRITE_MODE_FORCE_NEW
798 write_mode = WRITE_MODE_FORCE_NEW
797 packed, meta, append = self._map.write_v2(write_mode)
799 packed, meta, append = self._map.write_v2(write_mode)
798 if append:
800 if append:
799 docket = self.docket
801 docket = self.docket
800 data_filename = docket.data_filename()
802 data_filename = docket.data_filename()
801 # We mark it for backup to make sure a future `hg rollback` (or
803 # We mark it for backup to make sure a future `hg rollback` (or
802 # `hg recover`?) call find the data it needs to restore a
804 # `hg recover`?) call find the data it needs to restore a
803 # working repository.
805 # working repository.
804 #
806 #
805 # The backup can use a hardlink because the format is resistant
807 # The backup can use a hardlink because the format is resistant
806 # to trailing "dead" data.
808 # to trailing "dead" data.
807 if tr is not None:
809 if tr is not None:
808 tr.addbackup(data_filename, location=b'plain')
810 tr.addbackup(data_filename, location=b'plain')
809 with self._opener(data_filename, b'r+b') as fp:
811 with self._opener(data_filename, b'r+b') as fp:
810 fp.seek(docket.data_size)
812 fp.seek(docket.data_size)
811 assert fp.tell() == docket.data_size
813 assert fp.tell() == docket.data_size
812 written = fp.write(packed)
814 written = fp.write(packed)
813 if written is not None: # py2 may return None
815 if written is not None: # py2 may return None
814 assert written == len(packed), (written, len(packed))
816 assert written == len(packed), (written, len(packed))
815 docket.data_size += len(packed)
817 docket.data_size += len(packed)
816 docket.parents = self.parents()
818 docket.parents = self.parents()
817 docket.tree_metadata = meta
819 docket.tree_metadata = meta
818 st.write(docket.serialize())
820 st.write(docket.serialize())
819 st.close()
821 st.close()
820 else:
822 else:
821 self.write_v2_no_append(tr, st, meta, packed)
823 self.write_v2_no_append(tr, st, meta, packed)
822 # Reload from the newly-written file
824 # Reload from the newly-written file
823 util.clearcachedproperty(self, b"_map")
825 util.clearcachedproperty(self, b"_map")
824 self._dirtyparents = False
826 self._dirtyparents = False
825
827
826 ### code related to maintaining and accessing "extra" property
828 ### code related to maintaining and accessing "extra" property
827 # (e.g. "has_dir")
829 # (e.g. "has_dir")
828
830
829 @propertycache
831 @propertycache
830 def filefoldmap(self):
832 def filefoldmap(self):
831 """Returns a dictionary mapping normalized case paths to their
833 """Returns a dictionary mapping normalized case paths to their
832 non-normalized versions.
834 non-normalized versions.
833 """
835 """
834 return self._map.filefoldmapasdict()
836 return self._map.filefoldmapasdict()
835
837
836 def hastrackeddir(self, d):
838 def hastrackeddir(self, d):
837 return self._map.hastrackeddir(d)
839 return self._map.hastrackeddir(d)
838
840
839 def hasdir(self, d):
841 def hasdir(self, d):
840 return self._map.hasdir(d)
842 return self._map.hasdir(d)
841
843
842 @propertycache
844 @propertycache
843 def dirfoldmap(self):
845 def dirfoldmap(self):
844 f = {}
846 f = {}
845 normcase = util.normcase
847 normcase = util.normcase
846 for name in self._map.tracked_dirs():
848 for name in self._map.tracked_dirs():
847 f[normcase(name)] = name
849 f[normcase(name)] = name
848 return f
850 return f
849
851
850 ### code related to manipulation of entries and copy-sources
852 ### code related to manipulation of entries and copy-sources
851
853
852 def set_tracked(self, f):
854 def set_tracked(self, f):
853 return self._map.set_tracked(f)
855 return self._map.set_tracked(f)
854
856
855 def set_untracked(self, f):
857 def set_untracked(self, f):
856 return self._map.set_untracked(f)
858 return self._map.set_untracked(f)
857
859
858 def set_clean(self, filename, mode, size, mtime):
860 def set_clean(self, filename, mode, size, mtime):
859 self._map.set_clean(filename, mode, size, mtime)
861 self._map.set_clean(filename, mode, size, mtime)
860
862
861 def set_possibly_dirty(self, f):
863 def set_possibly_dirty(self, f):
862 self._map.set_possibly_dirty(f)
864 self._map.set_possibly_dirty(f)
863
865
864 def reset_state(
866 def reset_state(
865 self,
867 self,
866 filename,
868 filename,
867 wc_tracked=False,
869 wc_tracked=False,
868 p1_tracked=False,
870 p1_tracked=False,
869 p2_info=False,
871 p2_info=False,
870 has_meaningful_mtime=True,
872 has_meaningful_mtime=True,
871 parentfiledata=None,
873 parentfiledata=None,
872 ):
874 ):
873 return self._map.reset_state(
875 return self._map.reset_state(
874 filename,
876 filename,
875 wc_tracked,
877 wc_tracked,
876 p1_tracked,
878 p1_tracked,
877 p2_info,
879 p2_info,
878 has_meaningful_mtime,
880 has_meaningful_mtime,
879 parentfiledata,
881 parentfiledata,
880 )
882 )
@@ -1,408 +1,364
1 ==============================================================================
1 ==============================================================================
2 Check potential race conditions between a dirstate's read and other operations
2 Check potential race conditions between a dirstate's read and other operations
3 ==============================================================================
3 ==============================================================================
4
4
5 #testcases dirstate-v1 dirstate-v2-append dirstate-v2-rewrite
5 #testcases dirstate-v1 dirstate-v2-append dirstate-v2-rewrite
6 #testcases pre-all-read pre-some-read
6 #testcases pre-all-read pre-some-read
7
7
8 Some commands, like `hg status`, do not need to take the wlock but need to
8 Some commands, like `hg status`, do not need to take the wlock but need to
9 access dirstate data.
9 access dirstate data.
10 Other commands might update the dirstate data while this happens.
10 Other commands might update the dirstate data while this happens.
11
11
12 This can create issues if repository data is read in the wrong order, or for
12 This can create issues if repository data is read in the wrong order, or for
13 the dirstate-v2 format where the data is contained in multiple files.
13 the dirstate-v2 format where the data is contained in multiple files.
14
14
15 This test file is meant to test various cases where such parallel operations
15 This test file is meant to test various cases where such parallel operations
16 happen and make sure the reading process behaves fine. We do so with a `hg
16 happen and make sure the reading process behaves fine. We do so with a `hg
17 status` command since it is probably the most advanced of such read-only
17 status` command since it is probably the most advanced of such read-only
18 command.
18 command.
19
19
20 It bears simililarity with `tests/test-dirstate-status-race.t ` but tests a
20 It bears simililarity with `tests/test-dirstate-status-race.t ` but tests a
21 different type of race.
21 different type of race.
22
22
23 Setup
23 Setup
24 =====
24 =====
25
25
26 $ cat >> $HGRCPATH << EOF
26 $ cat >> $HGRCPATH << EOF
27 > [storage]
27 > [storage]
28 > dirstate-v2.slow-path=allow
28 > dirstate-v2.slow-path=allow
29 > EOF
29 > EOF
30
30
31 #if no-dirstate-v1
31 #if no-dirstate-v1
32 $ cat >> $HGRCPATH << EOF
32 $ cat >> $HGRCPATH << EOF
33 > [format]
33 > [format]
34 > use-dirstate-v2=yes
34 > use-dirstate-v2=yes
35 > EOF
35 > EOF
36 #else
36 #else
37 $ cat >> $HGRCPATH << EOF
37 $ cat >> $HGRCPATH << EOF
38 > [format]
38 > [format]
39 > use-dirstate-v2=no
39 > use-dirstate-v2=no
40 > EOF
40 > EOF
41 #endif
41 #endif
42
42
43 #if dirstate-v2-rewrite
43 #if dirstate-v2-rewrite
44 $ d2args="--config devel.dirstate.v2.data_update_mode=force-new"
44 $ d2args="--config devel.dirstate.v2.data_update_mode=force-new"
45 #endif
45 #endif
46 #if dirstate-v2-append
46 #if dirstate-v2-append
47 $ d2args="--config devel.dirstate.v2.data_update_mode=force-append"
47 $ d2args="--config devel.dirstate.v2.data_update_mode=force-append"
48 #endif
48 #endif
49
49
50
50
51 #if dirstate-v1
51 #if dirstate-v1
52 $ cfg="devel.sync.dirstate.pre-read-file"
52 $ cfg="devel.sync.dirstate.pre-read-file"
53 #else
53 #else
54 #if pre-all-read
54 #if pre-all-read
55 $ cfg="devel.sync.dirstate.pre-read-file"
55 $ cfg="devel.sync.dirstate.pre-read-file"
56 #else
56 #else
57 $ cfg="devel.sync.dirstate.post-docket-read-file"
57 $ cfg="devel.sync.dirstate.post-docket-read-file"
58 #endif
58 #endif
59 #endif
59 #endif
60
60
61 $ directories="dir dir/nested dir2"
61 $ directories="dir dir/nested dir2"
62 $ first_files="dir/nested/a dir/b dir/c dir/d dir2/e f"
62 $ first_files="dir/nested/a dir/b dir/c dir/d dir2/e f"
63 $ second_files="g dir/nested/h dir/i dir/j dir2/k dir2/l dir/nested/m"
63 $ second_files="g dir/nested/h dir/i dir/j dir2/k dir2/l dir/nested/m"
64 $ extra_files="dir/n dir/o p q"
64 $ extra_files="dir/n dir/o p q"
65
65
66 $ hg init reference-repo
66 $ hg init reference-repo
67 $ cd reference-repo
67 $ cd reference-repo
68 $ mkdir -p dir/nested dir2
68 $ mkdir -p dir/nested dir2
69 $ touch -t 200001010000 $first_files $directories
69 $ touch -t 200001010000 $first_files $directories
70 $ hg commit -Aqm "recreate a bunch of files to facilitate dirstate-v2 append"
70 $ hg commit -Aqm "recreate a bunch of files to facilitate dirstate-v2 append"
71 $ touch -t 200001010010 $second_files $directories
71 $ touch -t 200001010010 $second_files $directories
72 $ hg commit -Aqm "more files to have two commit"
72 $ hg commit -Aqm "more files to have two commit"
73 $ hg log -G -v
73 $ hg log -G -v
74 @ changeset: 1:9a86dcbfb938
74 @ changeset: 1:9a86dcbfb938
75 | tag: tip
75 | tag: tip
76 | user: test
76 | user: test
77 | date: Thu Jan 01 00:00:00 1970 +0000
77 | date: Thu Jan 01 00:00:00 1970 +0000
78 | files: dir/i dir/j dir/nested/h dir/nested/m dir2/k dir2/l g
78 | files: dir/i dir/j dir/nested/h dir/nested/m dir2/k dir2/l g
79 | description:
79 | description:
80 | more files to have two commit
80 | more files to have two commit
81 |
81 |
82 |
82 |
83 o changeset: 0:4f23db756b09
83 o changeset: 0:4f23db756b09
84 user: test
84 user: test
85 date: Thu Jan 01 00:00:00 1970 +0000
85 date: Thu Jan 01 00:00:00 1970 +0000
86 files: dir/b dir/c dir/d dir/nested/a dir2/e f
86 files: dir/b dir/c dir/d dir/nested/a dir2/e f
87 description:
87 description:
88 recreate a bunch of files to facilitate dirstate-v2 append
88 recreate a bunch of files to facilitate dirstate-v2 append
89
89
90
90
91 $ hg manifest
91 $ hg manifest
92 dir/b
92 dir/b
93 dir/c
93 dir/c
94 dir/d
94 dir/d
95 dir/i
95 dir/i
96 dir/j
96 dir/j
97 dir/nested/a
97 dir/nested/a
98 dir/nested/h
98 dir/nested/h
99 dir/nested/m
99 dir/nested/m
100 dir2/e
100 dir2/e
101 dir2/k
101 dir2/k
102 dir2/l
102 dir2/l
103 f
103 f
104 g
104 g
105
105
106 Add some unknown files and refresh the dirstate
106 Add some unknown files and refresh the dirstate
107
107
108 $ touch -t 200001010020 $extra_files
108 $ touch -t 200001010020 $extra_files
109 $ hg add dir/o
109 $ hg add dir/o
110 $ hg remove dir/nested/m
110 $ hg remove dir/nested/m
111
111
112 $ hg st --config devel.dirstate.v2.data_update_mode=force-new
112 $ hg st --config devel.dirstate.v2.data_update_mode=force-new
113 A dir/o
113 A dir/o
114 R dir/nested/m
114 R dir/nested/m
115 ? dir/n
115 ? dir/n
116 ? p
116 ? p
117 ? q
117 ? q
118 $ hg debugstate
118 $ hg debugstate
119 n 644 0 2000-01-01 00:00:00 dir/b
119 n 644 0 2000-01-01 00:00:00 dir/b
120 n 644 0 2000-01-01 00:00:00 dir/c
120 n 644 0 2000-01-01 00:00:00 dir/c
121 n 644 0 2000-01-01 00:00:00 dir/d
121 n 644 0 2000-01-01 00:00:00 dir/d
122 n 644 0 2000-01-01 00:10:00 dir/i
122 n 644 0 2000-01-01 00:10:00 dir/i
123 n 644 0 2000-01-01 00:10:00 dir/j
123 n 644 0 2000-01-01 00:10:00 dir/j
124 n 644 0 2000-01-01 00:00:00 dir/nested/a
124 n 644 0 2000-01-01 00:00:00 dir/nested/a
125 n 644 0 2000-01-01 00:10:00 dir/nested/h
125 n 644 0 2000-01-01 00:10:00 dir/nested/h
126 r ?????????????????????????????????? dir/nested/m (glob)
126 r ?????????????????????????????????? dir/nested/m (glob)
127 a ?????????????????????????????????? dir/o (glob)
127 a ?????????????????????????????????? dir/o (glob)
128 n 644 0 2000-01-01 00:00:00 dir2/e
128 n 644 0 2000-01-01 00:00:00 dir2/e
129 n 644 0 2000-01-01 00:10:00 dir2/k
129 n 644 0 2000-01-01 00:10:00 dir2/k
130 n 644 0 2000-01-01 00:10:00 dir2/l
130 n 644 0 2000-01-01 00:10:00 dir2/l
131 n 644 0 2000-01-01 00:00:00 f
131 n 644 0 2000-01-01 00:00:00 f
132 n 644 0 2000-01-01 00:10:00 g
132 n 644 0 2000-01-01 00:10:00 g
133 $ hg debugstate > ../reference
133 $ hg debugstate > ../reference
134 $ cd ..
134 $ cd ..
135
135
136 Actual Testing
136 Actual Testing
137 ==============
137 ==============
138
138
139 Race with a `hg add`
139 Race with a `hg add`
140 -------------------
140 -------------------
141
141
142 $ cp -a reference-repo race-with-add
142 $ cp -a reference-repo race-with-add
143 $ cd race-with-add
143 $ cd race-with-add
144
144
145 spin a `hg status` with some caches to update
145 spin a `hg status` with some caches to update
146
146
147 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
147 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
148 > --config rhg.on-unsupported=abort \
148 > --config rhg.on-unsupported=abort \
149 > --config ${cfg}=$TESTTMP/status-race-lock \
149 > --config ${cfg}=$TESTTMP/status-race-lock \
150 > &
150 > &
151 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
151 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
152
152
153 Add a file
153 Add a file
154
154
155 $ hg $d2args add dir/n
155 $ hg $d2args add dir/n
156 $ touch $TESTTMP/status-race-lock
156 $ touch $TESTTMP/status-race-lock
157 $ wait
157 $ wait
158
158
159 The file should in a "added" state
159 The file should in a "added" state
160
160
161 $ hg status
161 $ hg status
162 A dir/n
162 A dir/n
163 A dir/o
163 A dir/o
164 R dir/nested/m
164 R dir/nested/m
165 ? p
165 ? p
166 ? q
166 ? q
167
167
168 The status process should return a consistent result and not crash.
168 The status process should return a consistent result and not crash.
169
169
170 #if dirstate-v1
170 #if rust dirstate-v2-append pre-some-read
171 $ cat $TESTTMP/status-race-lock.out
172 A dir/n
173 A dir/o
174 R dir/nested/m
175 ? p
176 ? q
177 #else
178 #if rhg pre-some-read dirstate-v2-append
179 $ cat $TESTTMP/status-race-lock.out
180 A dir/o
181 R dir/nested/m
182 ? dir/n
183 ? p
184 ? q
185 #else
186 #if rust no-rhg dirstate-v2-append
187 $ cat $TESTTMP/status-race-lock.out
171 $ cat $TESTTMP/status-race-lock.out
188 A dir/o
172 A dir/o
189 R dir/nested/m
173 R dir/nested/m
190 ? dir/n
174 ? dir/n
191 ? p
175 ? p
192 ? q
176 ? q
193 #else
177 #else
194 $ cat $TESTTMP/status-race-lock.out
178 $ cat $TESTTMP/status-race-lock.out
195 A dir/n
179 A dir/n
196 A dir/o
180 A dir/o
197 R dir/nested/m
181 R dir/nested/m
198 ? p
182 ? p
199 ? q
183 ? q
200 #endif
184 #endif
201 #endif
202 #endif
203 $ cat $TESTTMP/status-race-lock.log
185 $ cat $TESTTMP/status-race-lock.log
204
186
205 final cleanup
187 final cleanup
206
188
207 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
189 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
208 $ cd ..
190 $ cd ..
209
191
210 Race with a `hg commit`
192 Race with a `hg commit`
211 -----------------------
193 -----------------------
212
194
213 $ cp -a reference-repo race-with-commit
195 $ cp -a reference-repo race-with-commit
214 $ cd race-with-commit
196 $ cd race-with-commit
215
197
216 spin a `hg status with some cache to update
198 spin a `hg status with some cache to update
217
199
218 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
200 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
219 > --config rhg.on-unsupported=abort \
201 > --config rhg.on-unsupported=abort \
220 > --config ${cfg}=$TESTTMP/status-race-lock \
202 > --config ${cfg}=$TESTTMP/status-race-lock \
221 > &
203 > &
222 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
204 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
223
205
224 Add a do a commit
206 Add a do a commit
225
207
226 $ hg status
208 $ hg status
227 A dir/o
209 A dir/o
228 R dir/nested/m
210 R dir/nested/m
229 ? dir/n
211 ? dir/n
230 ? p
212 ? p
231 ? q
213 ? q
232 $ hg $d2args commit -m 'racing commit'
214 $ hg $d2args commit -m 'racing commit'
233 $ touch $TESTTMP/status-race-lock
215 $ touch $TESTTMP/status-race-lock
234 $ wait
216 $ wait
235
217
236 commit was created, and status is now clean
218 commit was created, and status is now clean
237
219
238 $ hg log -GT '{node|short} {desc}\n'
220 $ hg log -GT '{node|short} {desc}\n'
239 @ 02a67a77ee9b racing commit
221 @ 02a67a77ee9b racing commit
240 |
222 |
241 o 9a86dcbfb938 more files to have two commit
223 o 9a86dcbfb938 more files to have two commit
242 |
224 |
243 o 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
225 o 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
244
226
245 $ hg status
227 $ hg status
246 ? dir/n
228 ? dir/n
247 ? p
229 ? p
248 ? q
230 ? q
249
231
250 The status process should return a consistent result and not crash.
232 The status process should return a consistent result and not crash.
251
233
252 #if no-rhg
234 #if dirstate-v1
235 $ cat $TESTTMP/status-race-lock.out
236 ? dir/n
237 ? p
238 ? q
239 #endif
240 #if dirstate-v2
253 $ cat $TESTTMP/status-race-lock.out
241 $ cat $TESTTMP/status-race-lock.out
254 A dir/o
242 A dir/o
255 R dir/nested/m
243 R dir/nested/m
256 ? dir/n
244 ? dir/n
257 ? p
245 ? p
258 ? q
246 ? q
259 $ cat $TESTTMP/status-race-lock.log
260 #else
261 #if pre-some-read dirstate-v2-append
262 $ cat $TESTTMP/status-race-lock.out
263 A dir/o
264 R dir/nested/m
265 ? dir/n
266 ? p
267 ? q
268 $ cat $TESTTMP/status-race-lock.log
269 #else
270 $ cat $TESTTMP/status-race-lock.out
271 ? dir/n
272 ? p
273 ? q
274 $ cat $TESTTMP/status-race-lock.log
275 #endif
276 #endif
247 #endif
277
248
278 final cleanup
249 final cleanup
279
250
280 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
251 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
281 $ cd ..
252 $ cd ..
282
253
283 Race with a `hg update`
254 Race with a `hg update`
284 -----------------------
255 -----------------------
285
256
286 $ cp -a reference-repo race-with-update
257 $ cp -a reference-repo race-with-update
287 $ cd race-with-update
258 $ cd race-with-update
288
259
289 spin a `hg status` with some caches to update
260 spin a `hg status` with some caches to update
290
261
291 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
262 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
292 > --config rhg.on-unsupported=abort \
263 > --config rhg.on-unsupported=abort \
293 > --config ${cfg}=$TESTTMP/status-race-lock \
264 > --config ${cfg}=$TESTTMP/status-race-lock \
294 > &
265 > &
295 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
266 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
296 do an update
267 do an update
297
268
298 $ hg status
269 $ hg status
299 A dir/o
270 A dir/o
300 R dir/nested/m
271 R dir/nested/m
301 ? dir/n
272 ? dir/n
302 ? p
273 ? p
303 ? q
274 ? q
304 $ hg log -GT '{node|short} {desc}\n'
275 $ hg log -GT '{node|short} {desc}\n'
305 @ 9a86dcbfb938 more files to have two commit
276 @ 9a86dcbfb938 more files to have two commit
306 |
277 |
307 o 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
278 o 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
308
279
309 $ hg $d2args update --merge ".~1"
280 $ hg $d2args update --merge ".~1"
310 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
281 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
311 $ touch $TESTTMP/status-race-lock
282 $ touch $TESTTMP/status-race-lock
312 $ wait
283 $ wait
313 $ hg log -GT '{node|short} {desc}\n'
284 $ hg log -GT '{node|short} {desc}\n'
314 o 9a86dcbfb938 more files to have two commit
285 o 9a86dcbfb938 more files to have two commit
315 |
286 |
316 @ 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
287 @ 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
317
288
318 $ hg status
289 $ hg status
319 A dir/o
290 A dir/o
320 ? dir/n
291 ? dir/n
321 ? p
292 ? p
322 ? q
293 ? q
323
294
324 The status process should return a consistent result and not crash.
295 The status process should return a consistent result and not crash.
325
296
326 #if rhg dirstate-v2-append pre-some-read
297 #if rust dirstate-v2-append pre-some-read
327 $ cat $TESTTMP/status-race-lock.out
328 A dir/o
329 R dir/nested/m
330 ! dir/i
331 ! dir/j
332 ! dir/nested/h
333 ! dir2/k
334 ! dir2/l
335 ! g
336 ? dir/n
337 ? p
338 ? q
339 #else
340 #if rust no-rhg dirstate-v2-append
341 $ cat $TESTTMP/status-race-lock.out
298 $ cat $TESTTMP/status-race-lock.out
342 A dir/o
299 A dir/o
343 R dir/nested/m
300 R dir/nested/m
344 ! dir/i
301 ! dir/i
345 ! dir/j
302 ! dir/j
346 ! dir/nested/h
303 ! dir/nested/h
347 ! dir2/k
304 ! dir2/k
348 ! dir2/l
305 ! dir2/l
349 ! g
306 ! g
350 ? dir/n
307 ? dir/n
351 ? p
308 ? p
352 ? q
309 ? q
353 #else
310 #else
354 $ cat $TESTTMP/status-race-lock.out
311 $ cat $TESTTMP/status-race-lock.out
355 A dir/o
312 A dir/o
356 ? dir/n
313 ? dir/n
357 ? p
314 ? p
358 ? q
315 ? q
359 #endif
316 #endif
360 #endif
361 $ cat $TESTTMP/status-race-lock.log
317 $ cat $TESTTMP/status-race-lock.log
362
318
363 final cleanup
319 final cleanup
364
320
365 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
321 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
366 $ cd ..
322 $ cd ..
367
323
368 Race with a cache updating `hg status`
324 Race with a cache updating `hg status`
369 --------------------------------------
325 --------------------------------------
370
326
371 It is interesting to race with "read-only" operation (that still update its cache)
327 It is interesting to race with "read-only" operation (that still update its cache)
372
328
373 $ cp -a reference-repo race-with-status
329 $ cp -a reference-repo race-with-status
374 $ cd race-with-status
330 $ cd race-with-status
375
331
376 spin a `hg status` with some caches to update
332 spin a `hg status` with some caches to update
377
333
378 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
334 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
379 > --config rhg.on-unsupported=abort \
335 > --config rhg.on-unsupported=abort \
380 > --config ${cfg}=$TESTTMP/status-race-lock \
336 > --config ${cfg}=$TESTTMP/status-race-lock \
381 > &
337 > &
382 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
338 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
383 do an update
339 do an update
384
340
385 $ touch -t 200001020006 f
341 $ touch -t 200001020006 f
386 $ hg $d2args status
342 $ hg $d2args status
387 A dir/o
343 A dir/o
388 R dir/nested/m
344 R dir/nested/m
389 ? dir/n
345 ? dir/n
390 ? p
346 ? p
391 ? q
347 ? q
392 $ touch $TESTTMP/status-race-lock
348 $ touch $TESTTMP/status-race-lock
393 $ wait
349 $ wait
394
350
395 The status process should return a consistent result and not crash.
351 The status process should return a consistent result and not crash.
396
352
397 $ cat $TESTTMP/status-race-lock.out
353 $ cat $TESTTMP/status-race-lock.out
398 A dir/o
354 A dir/o
399 R dir/nested/m
355 R dir/nested/m
400 ? dir/n
356 ? dir/n
401 ? p
357 ? p
402 ? q
358 ? q
403 $ cat $TESTTMP/status-race-lock.log
359 $ cat $TESTTMP/status-race-lock.log
404
360
405 final cleanup
361 final cleanup
406
362
407 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
363 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
408 $ cd ..
364 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now