##// END OF EJS Templates
rhg: remember the inode of .hg/dirstate...
Raphaël Gomès -
r51140:dbe09fb0 stable
parent child Browse files
Show More
@@ -1,737 +1,750 b''
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
6
7 from .i18n import _
7 from .i18n import _
8
8
9 from . import (
9 from . import (
10 error,
10 error,
11 pathutil,
11 pathutil,
12 policy,
12 policy,
13 testing,
13 testing,
14 txnutil,
14 txnutil,
15 util,
15 util,
16 )
16 )
17
17
18 from .dirstateutils import (
18 from .dirstateutils import (
19 docket as docketmod,
19 docket as docketmod,
20 v2,
20 v2,
21 )
21 )
22
22
23 parsers = policy.importmod('parsers')
23 parsers = policy.importmod('parsers')
24 rustmod = policy.importrust('dirstate')
24 rustmod = policy.importrust('dirstate')
25
25
26 propertycache = util.propertycache
26 propertycache = util.propertycache
27
27
28 if rustmod is None:
28 if rustmod is None:
29 DirstateItem = parsers.DirstateItem
29 DirstateItem = parsers.DirstateItem
30 else:
30 else:
31 DirstateItem = rustmod.DirstateItem
31 DirstateItem = rustmod.DirstateItem
32
32
33 rangemask = 0x7FFFFFFF
33 rangemask = 0x7FFFFFFF
34
34
35 WRITE_MODE_AUTO = 0
35 WRITE_MODE_AUTO = 0
36 WRITE_MODE_FORCE_NEW = 1
36 WRITE_MODE_FORCE_NEW = 1
37 WRITE_MODE_FORCE_APPEND = 2
37 WRITE_MODE_FORCE_APPEND = 2
38
38
39
39
40 V2_MAX_READ_ATTEMPTS = 5
40 V2_MAX_READ_ATTEMPTS = 5
41
41
42
42
43 class _dirstatemapcommon:
43 class _dirstatemapcommon:
44 """
44 """
45 Methods that are identical for both implementations of the dirstatemap
45 Methods that are identical for both implementations of the dirstatemap
46 class, with and without Rust extensions enabled.
46 class, with and without Rust extensions enabled.
47 """
47 """
48
48
49 # please pytype
49 # please pytype
50
50
51 _map = None
51 _map = None
52 copymap = None
52 copymap = None
53
53
54 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
54 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
55 self._use_dirstate_v2 = use_dirstate_v2
55 self._use_dirstate_v2 = use_dirstate_v2
56 self._nodeconstants = nodeconstants
56 self._nodeconstants = nodeconstants
57 self._ui = ui
57 self._ui = ui
58 self._opener = opener
58 self._opener = opener
59 self._root = root
59 self._root = root
60 self._filename = b'dirstate'
60 self._filename = b'dirstate'
61 self._nodelen = 20 # Also update Rust code when changing this!
61 self._nodelen = 20 # Also update Rust code when changing this!
62 self._parents = None
62 self._parents = None
63 self._dirtyparents = False
63 self._dirtyparents = False
64 self._docket = None
64 self._docket = None
65 write_mode = ui.config(b"devel", b"dirstate.v2.data_update_mode")
65 write_mode = ui.config(b"devel", b"dirstate.v2.data_update_mode")
66 if write_mode == b"auto":
66 if write_mode == b"auto":
67 self._write_mode = WRITE_MODE_AUTO
67 self._write_mode = WRITE_MODE_AUTO
68 elif write_mode == b"force-append":
68 elif write_mode == b"force-append":
69 self._write_mode = WRITE_MODE_FORCE_APPEND
69 self._write_mode = WRITE_MODE_FORCE_APPEND
70 elif write_mode == b"force-new":
70 elif write_mode == b"force-new":
71 self._write_mode = WRITE_MODE_FORCE_NEW
71 self._write_mode = WRITE_MODE_FORCE_NEW
72 else:
72 else:
73 # unknown value, fallback to default
73 # unknown value, fallback to default
74 self._write_mode = WRITE_MODE_AUTO
74 self._write_mode = WRITE_MODE_AUTO
75
75
76 # for consistent view between _pl() and _read() invocations
76 # for consistent view between _pl() and _read() invocations
77 self._pendingmode = None
77 self._pendingmode = None
78
78
79 def _set_identity(self):
79 def _set_identity(self):
80 # ignore HG_PENDING because identity is used only for writing
80 # ignore HG_PENDING because identity is used only for writing
81 file_path = self._opener.join(self._filename)
81 file_path = self._opener.join(self._filename)
82 self.identity = util.filestat.frompath(file_path)
82 self.identity = util.filestat.frompath(file_path)
83
83
84 def preload(self):
84 def preload(self):
85 """Loads the underlying data, if it's not already loaded"""
85 """Loads the underlying data, if it's not already loaded"""
86 self._map
86 self._map
87
87
88 def get(self, key, default=None):
88 def get(self, key, default=None):
89 return self._map.get(key, default)
89 return self._map.get(key, default)
90
90
91 def __len__(self):
91 def __len__(self):
92 return len(self._map)
92 return len(self._map)
93
93
94 def __iter__(self):
94 def __iter__(self):
95 return iter(self._map)
95 return iter(self._map)
96
96
97 def __contains__(self, key):
97 def __contains__(self, key):
98 return key in self._map
98 return key in self._map
99
99
100 def __getitem__(self, item):
100 def __getitem__(self, item):
101 return self._map[item]
101 return self._map[item]
102
102
103 ### disk interaction
103 ### disk interaction
104
104
105 def _opendirstatefile(self):
105 def _opendirstatefile(self):
106 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
106 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
107 if self._pendingmode is not None and self._pendingmode != mode:
107 if self._pendingmode is not None and self._pendingmode != mode:
108 fp.close()
108 fp.close()
109 raise error.Abort(
109 raise error.Abort(
110 _(b'working directory state may be changed parallelly')
110 _(b'working directory state may be changed parallelly')
111 )
111 )
112 self._pendingmode = mode
112 self._pendingmode = mode
113 return fp
113 return fp
114
114
115 def _readdirstatefile(self, size=-1):
115 def _readdirstatefile(self, size=-1):
116 try:
116 try:
117 with self._opendirstatefile() as fp:
117 with self._opendirstatefile() as fp:
118 return fp.read(size)
118 return fp.read(size)
119 except FileNotFoundError:
119 except FileNotFoundError:
120 # File doesn't exist, so the current state is empty
120 # File doesn't exist, so the current state is empty
121 return b''
121 return b''
122
122
123 @property
123 @property
124 def docket(self):
124 def docket(self):
125 if not self._docket:
125 if not self._docket:
126 if not self._use_dirstate_v2:
126 if not self._use_dirstate_v2:
127 raise error.ProgrammingError(
127 raise error.ProgrammingError(
128 b'dirstate only has a docket in v2 format'
128 b'dirstate only has a docket in v2 format'
129 )
129 )
130 self._set_identity()
130 self._set_identity()
131 self._docket = docketmod.DirstateDocket.parse(
131 self._docket = docketmod.DirstateDocket.parse(
132 self._readdirstatefile(), self._nodeconstants
132 self._readdirstatefile(), self._nodeconstants
133 )
133 )
134 return self._docket
134 return self._docket
135
135
136 def _read_v2_data(self):
136 def _read_v2_data(self):
137 data = None
137 data = None
138 attempts = 0
138 attempts = 0
139 while attempts < V2_MAX_READ_ATTEMPTS:
139 while attempts < V2_MAX_READ_ATTEMPTS:
140 attempts += 1
140 attempts += 1
141 try:
141 try:
142 # TODO: use mmap when possible
142 # TODO: use mmap when possible
143 data = self._opener.read(self.docket.data_filename())
143 data = self._opener.read(self.docket.data_filename())
144 except FileNotFoundError:
144 except FileNotFoundError:
145 # read race detected between docket and data file
145 # read race detected between docket and data file
146 # reload the docket and retry
146 # reload the docket and retry
147 self._docket = None
147 self._docket = None
148 if data is None:
148 if data is None:
149 assert attempts >= V2_MAX_READ_ATTEMPTS
149 assert attempts >= V2_MAX_READ_ATTEMPTS
150 msg = b"dirstate read race happened %d times in a row"
150 msg = b"dirstate read race happened %d times in a row"
151 msg %= attempts
151 msg %= attempts
152 raise error.Abort(msg)
152 raise error.Abort(msg)
153 return self._opener.read(self.docket.data_filename())
153 return self._opener.read(self.docket.data_filename())
154
154
155 def write_v2_no_append(self, tr, st, meta, packed):
155 def write_v2_no_append(self, tr, st, meta, packed):
156 old_docket = self.docket
156 old_docket = self.docket
157 new_docket = docketmod.DirstateDocket.with_new_uuid(
157 new_docket = docketmod.DirstateDocket.with_new_uuid(
158 self.parents(), len(packed), meta
158 self.parents(), len(packed), meta
159 )
159 )
160 if old_docket.uuid == new_docket.uuid:
160 if old_docket.uuid == new_docket.uuid:
161 raise error.ProgrammingError(b'dirstate docket name collision')
161 raise error.ProgrammingError(b'dirstate docket name collision')
162 data_filename = new_docket.data_filename()
162 data_filename = new_docket.data_filename()
163 self._opener.write(data_filename, packed)
163 self._opener.write(data_filename, packed)
164 # Write the new docket after the new data file has been
164 # Write the new docket after the new data file has been
165 # written. Because `st` was opened with `atomictemp=True`,
165 # written. Because `st` was opened with `atomictemp=True`,
166 # the actual `.hg/dirstate` file is only affected on close.
166 # the actual `.hg/dirstate` file is only affected on close.
167 st.write(new_docket.serialize())
167 st.write(new_docket.serialize())
168 st.close()
168 st.close()
169 # Remove the old data file after the new docket pointing to
169 # Remove the old data file after the new docket pointing to
170 # the new data file was written.
170 # the new data file was written.
171 if old_docket.uuid:
171 if old_docket.uuid:
172 data_filename = old_docket.data_filename()
172 data_filename = old_docket.data_filename()
173 unlink = lambda _tr=None: self._opener.unlink(data_filename)
173 unlink = lambda _tr=None: self._opener.unlink(data_filename)
174 if tr:
174 if tr:
175 category = b"dirstate-v2-clean-" + old_docket.uuid
175 category = b"dirstate-v2-clean-" + old_docket.uuid
176 tr.addpostclose(category, unlink)
176 tr.addpostclose(category, unlink)
177 else:
177 else:
178 unlink()
178 unlink()
179 self._docket = new_docket
179 self._docket = new_docket
180
180
181 ### reading/setting parents
181 ### reading/setting parents
182
182
183 def parents(self):
183 def parents(self):
184 if not self._parents:
184 if not self._parents:
185 if self._use_dirstate_v2:
185 if self._use_dirstate_v2:
186 self._parents = self.docket.parents
186 self._parents = self.docket.parents
187 else:
187 else:
188 read_len = self._nodelen * 2
188 read_len = self._nodelen * 2
189 st = self._readdirstatefile(read_len)
189 st = self._readdirstatefile(read_len)
190 l = len(st)
190 l = len(st)
191 if l == read_len:
191 if l == read_len:
192 self._parents = (
192 self._parents = (
193 st[: self._nodelen],
193 st[: self._nodelen],
194 st[self._nodelen : 2 * self._nodelen],
194 st[self._nodelen : 2 * self._nodelen],
195 )
195 )
196 elif l == 0:
196 elif l == 0:
197 self._parents = (
197 self._parents = (
198 self._nodeconstants.nullid,
198 self._nodeconstants.nullid,
199 self._nodeconstants.nullid,
199 self._nodeconstants.nullid,
200 )
200 )
201 else:
201 else:
202 raise error.Abort(
202 raise error.Abort(
203 _(b'working directory state appears damaged!')
203 _(b'working directory state appears damaged!')
204 )
204 )
205
205
206 return self._parents
206 return self._parents
207
207
208
208
209 class dirstatemap(_dirstatemapcommon):
209 class dirstatemap(_dirstatemapcommon):
210 """Map encapsulating the dirstate's contents.
210 """Map encapsulating the dirstate's contents.
211
211
212 The dirstate contains the following state:
212 The dirstate contains the following state:
213
213
214 - `identity` is the identity of the dirstate file, which can be used to
214 - `identity` is the identity of the dirstate file, which can be used to
215 detect when changes have occurred to the dirstate file.
215 detect when changes have occurred to the dirstate file.
216
216
217 - `parents` is a pair containing the parents of the working copy. The
217 - `parents` is a pair containing the parents of the working copy. The
218 parents are updated by calling `setparents`.
218 parents are updated by calling `setparents`.
219
219
220 - the state map maps filenames to tuples of (state, mode, size, mtime),
220 - the state map maps filenames to tuples of (state, mode, size, mtime),
221 where state is a single character representing 'normal', 'added',
221 where state is a single character representing 'normal', 'added',
222 'removed', or 'merged'. It is read by treating the dirstate as a
222 'removed', or 'merged'. It is read by treating the dirstate as a
223 dict. File state is updated by calling various methods (see each
223 dict. File state is updated by calling various methods (see each
224 documentation for details):
224 documentation for details):
225
225
226 - `reset_state`,
226 - `reset_state`,
227 - `set_tracked`
227 - `set_tracked`
228 - `set_untracked`
228 - `set_untracked`
229 - `set_clean`
229 - `set_clean`
230 - `set_possibly_dirty`
230 - `set_possibly_dirty`
231
231
232 - `copymap` maps destination filenames to their source filename.
232 - `copymap` maps destination filenames to their source filename.
233
233
234 The dirstate also provides the following views onto the state:
234 The dirstate also provides the following views onto the state:
235
235
236 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
236 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
237 form that they appear as in the dirstate.
237 form that they appear as in the dirstate.
238
238
239 - `dirfoldmap` is a dict mapping normalized directory names to the
239 - `dirfoldmap` is a dict mapping normalized directory names to the
240 denormalized form that they appear as in the dirstate.
240 denormalized form that they appear as in the dirstate.
241 """
241 """
242
242
243 ### Core data storage and access
243 ### Core data storage and access
244
244
245 @propertycache
245 @propertycache
246 def _map(self):
246 def _map(self):
247 self._map = {}
247 self._map = {}
248 self.read()
248 self.read()
249 return self._map
249 return self._map
250
250
251 @propertycache
251 @propertycache
252 def copymap(self):
252 def copymap(self):
253 self.copymap = {}
253 self.copymap = {}
254 self._map
254 self._map
255 return self.copymap
255 return self.copymap
256
256
257 def clear(self):
257 def clear(self):
258 self._map.clear()
258 self._map.clear()
259 self.copymap.clear()
259 self.copymap.clear()
260 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
260 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
261 util.clearcachedproperty(self, b"_dirs")
261 util.clearcachedproperty(self, b"_dirs")
262 util.clearcachedproperty(self, b"_alldirs")
262 util.clearcachedproperty(self, b"_alldirs")
263 util.clearcachedproperty(self, b"filefoldmap")
263 util.clearcachedproperty(self, b"filefoldmap")
264 util.clearcachedproperty(self, b"dirfoldmap")
264 util.clearcachedproperty(self, b"dirfoldmap")
265
265
266 def items(self):
266 def items(self):
267 return self._map.items()
267 return self._map.items()
268
268
269 # forward for python2,3 compat
269 # forward for python2,3 compat
270 iteritems = items
270 iteritems = items
271
271
272 def debug_iter(self, all):
272 def debug_iter(self, all):
273 """
273 """
274 Return an iterator of (filename, state, mode, size, mtime) tuples
274 Return an iterator of (filename, state, mode, size, mtime) tuples
275
275
276 `all` is unused when Rust is not enabled
276 `all` is unused when Rust is not enabled
277 """
277 """
278 for (filename, item) in self.items():
278 for (filename, item) in self.items():
279 yield (filename, item.state, item.mode, item.size, item.mtime)
279 yield (filename, item.state, item.mode, item.size, item.mtime)
280
280
281 def keys(self):
281 def keys(self):
282 return self._map.keys()
282 return self._map.keys()
283
283
284 ### reading/setting parents
284 ### reading/setting parents
285
285
286 def setparents(self, p1, p2, fold_p2=False):
286 def setparents(self, p1, p2, fold_p2=False):
287 self._parents = (p1, p2)
287 self._parents = (p1, p2)
288 self._dirtyparents = True
288 self._dirtyparents = True
289 copies = {}
289 copies = {}
290 if fold_p2:
290 if fold_p2:
291 for f, s in self._map.items():
291 for f, s in self._map.items():
292 # Discard "merged" markers when moving away from a merge state
292 # Discard "merged" markers when moving away from a merge state
293 if s.p2_info:
293 if s.p2_info:
294 source = self.copymap.pop(f, None)
294 source = self.copymap.pop(f, None)
295 if source:
295 if source:
296 copies[f] = source
296 copies[f] = source
297 s.drop_merge_data()
297 s.drop_merge_data()
298 return copies
298 return copies
299
299
300 ### disk interaction
300 ### disk interaction
301
301
302 def read(self):
302 def read(self):
303 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
303 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
304 if self._use_dirstate_v2:
304 if self._use_dirstate_v2:
305
305
306 if not self.docket.uuid:
306 if not self.docket.uuid:
307 return
307 return
308 testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
308 testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
309 st = self._read_v2_data()
309 st = self._read_v2_data()
310 else:
310 else:
311 self._set_identity()
311 self._set_identity()
312 st = self._readdirstatefile()
312 st = self._readdirstatefile()
313
313
314 if not st:
314 if not st:
315 return
315 return
316
316
317 # TODO: adjust this estimate for dirstate-v2
317 # TODO: adjust this estimate for dirstate-v2
318 if util.safehasattr(parsers, b'dict_new_presized'):
318 if util.safehasattr(parsers, b'dict_new_presized'):
319 # Make an estimate of the number of files in the dirstate based on
319 # Make an estimate of the number of files in the dirstate based on
320 # its size. This trades wasting some memory for avoiding costly
320 # its size. This trades wasting some memory for avoiding costly
321 # resizes. Each entry have a prefix of 17 bytes followed by one or
321 # resizes. Each entry have a prefix of 17 bytes followed by one or
322 # two path names. Studies on various large-scale real-world repositories
322 # two path names. Studies on various large-scale real-world repositories
323 # found 54 bytes a reasonable upper limit for the average path names.
323 # found 54 bytes a reasonable upper limit for the average path names.
324 # Copy entries are ignored for the sake of this estimate.
324 # Copy entries are ignored for the sake of this estimate.
325 self._map = parsers.dict_new_presized(len(st) // 71)
325 self._map = parsers.dict_new_presized(len(st) // 71)
326
326
327 # Python's garbage collector triggers a GC each time a certain number
327 # Python's garbage collector triggers a GC each time a certain number
328 # of container objects (the number being defined by
328 # of container objects (the number being defined by
329 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
329 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
330 # for each file in the dirstate. The C version then immediately marks
330 # for each file in the dirstate. The C version then immediately marks
331 # them as not to be tracked by the collector. However, this has no
331 # them as not to be tracked by the collector. However, this has no
332 # effect on when GCs are triggered, only on what objects the GC looks
332 # effect on when GCs are triggered, only on what objects the GC looks
333 # into. This means that O(number of files) GCs are unavoidable.
333 # into. This means that O(number of files) GCs are unavoidable.
334 # Depending on when in the process's lifetime the dirstate is parsed,
334 # Depending on when in the process's lifetime the dirstate is parsed,
335 # this can get very expensive. As a workaround, disable GC while
335 # this can get very expensive. As a workaround, disable GC while
336 # parsing the dirstate.
336 # parsing the dirstate.
337 #
337 #
338 # (we cannot decorate the function directly since it is in a C module)
338 # (we cannot decorate the function directly since it is in a C module)
339 if self._use_dirstate_v2:
339 if self._use_dirstate_v2:
340 p = self.docket.parents
340 p = self.docket.parents
341 meta = self.docket.tree_metadata
341 meta = self.docket.tree_metadata
342 parse_dirstate = util.nogc(v2.parse_dirstate)
342 parse_dirstate = util.nogc(v2.parse_dirstate)
343 parse_dirstate(self._map, self.copymap, st, meta)
343 parse_dirstate(self._map, self.copymap, st, meta)
344 else:
344 else:
345 parse_dirstate = util.nogc(parsers.parse_dirstate)
345 parse_dirstate = util.nogc(parsers.parse_dirstate)
346 p = parse_dirstate(self._map, self.copymap, st)
346 p = parse_dirstate(self._map, self.copymap, st)
347 if not self._dirtyparents:
347 if not self._dirtyparents:
348 self.setparents(*p)
348 self.setparents(*p)
349
349
350 # Avoid excess attribute lookups by fast pathing certain checks
350 # Avoid excess attribute lookups by fast pathing certain checks
351 self.__contains__ = self._map.__contains__
351 self.__contains__ = self._map.__contains__
352 self.__getitem__ = self._map.__getitem__
352 self.__getitem__ = self._map.__getitem__
353 self.get = self._map.get
353 self.get = self._map.get
354
354
355 def write(self, tr, st):
355 def write(self, tr, st):
356 if self._use_dirstate_v2:
356 if self._use_dirstate_v2:
357 packed, meta = v2.pack_dirstate(self._map, self.copymap)
357 packed, meta = v2.pack_dirstate(self._map, self.copymap)
358 self.write_v2_no_append(tr, st, meta, packed)
358 self.write_v2_no_append(tr, st, meta, packed)
359 else:
359 else:
360 packed = parsers.pack_dirstate(
360 packed = parsers.pack_dirstate(
361 self._map, self.copymap, self.parents()
361 self._map, self.copymap, self.parents()
362 )
362 )
363 st.write(packed)
363 st.write(packed)
364 st.close()
364 st.close()
365 self._dirtyparents = False
365 self._dirtyparents = False
366
366
367 @propertycache
367 @propertycache
368 def identity(self):
368 def identity(self):
369 self._map
369 self._map
370 return self.identity
370 return self.identity
371
371
372 ### code related to maintaining and accessing "extra" property
372 ### code related to maintaining and accessing "extra" property
373 # (e.g. "has_dir")
373 # (e.g. "has_dir")
374
374
375 def _dirs_incr(self, filename, old_entry=None):
375 def _dirs_incr(self, filename, old_entry=None):
376 """increment the dirstate counter if applicable"""
376 """increment the dirstate counter if applicable"""
377 if (
377 if (
378 old_entry is None or old_entry.removed
378 old_entry is None or old_entry.removed
379 ) and "_dirs" in self.__dict__:
379 ) and "_dirs" in self.__dict__:
380 self._dirs.addpath(filename)
380 self._dirs.addpath(filename)
381 if old_entry is None and "_alldirs" in self.__dict__:
381 if old_entry is None and "_alldirs" in self.__dict__:
382 self._alldirs.addpath(filename)
382 self._alldirs.addpath(filename)
383
383
384 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
384 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
385 """decrement the dirstate counter if applicable"""
385 """decrement the dirstate counter if applicable"""
386 if old_entry is not None:
386 if old_entry is not None:
387 if "_dirs" in self.__dict__ and not old_entry.removed:
387 if "_dirs" in self.__dict__ and not old_entry.removed:
388 self._dirs.delpath(filename)
388 self._dirs.delpath(filename)
389 if "_alldirs" in self.__dict__ and not remove_variant:
389 if "_alldirs" in self.__dict__ and not remove_variant:
390 self._alldirs.delpath(filename)
390 self._alldirs.delpath(filename)
391 elif remove_variant and "_alldirs" in self.__dict__:
391 elif remove_variant and "_alldirs" in self.__dict__:
392 self._alldirs.addpath(filename)
392 self._alldirs.addpath(filename)
393 if "filefoldmap" in self.__dict__:
393 if "filefoldmap" in self.__dict__:
394 normed = util.normcase(filename)
394 normed = util.normcase(filename)
395 self.filefoldmap.pop(normed, None)
395 self.filefoldmap.pop(normed, None)
396
396
397 @propertycache
397 @propertycache
398 def filefoldmap(self):
398 def filefoldmap(self):
399 """Returns a dictionary mapping normalized case paths to their
399 """Returns a dictionary mapping normalized case paths to their
400 non-normalized versions.
400 non-normalized versions.
401 """
401 """
402 try:
402 try:
403 makefilefoldmap = parsers.make_file_foldmap
403 makefilefoldmap = parsers.make_file_foldmap
404 except AttributeError:
404 except AttributeError:
405 pass
405 pass
406 else:
406 else:
407 return makefilefoldmap(
407 return makefilefoldmap(
408 self._map, util.normcasespec, util.normcasefallback
408 self._map, util.normcasespec, util.normcasefallback
409 )
409 )
410
410
411 f = {}
411 f = {}
412 normcase = util.normcase
412 normcase = util.normcase
413 for name, s in self._map.items():
413 for name, s in self._map.items():
414 if not s.removed:
414 if not s.removed:
415 f[normcase(name)] = name
415 f[normcase(name)] = name
416 f[b'.'] = b'.' # prevents useless util.fspath() invocation
416 f[b'.'] = b'.' # prevents useless util.fspath() invocation
417 return f
417 return f
418
418
419 @propertycache
419 @propertycache
420 def dirfoldmap(self):
420 def dirfoldmap(self):
421 f = {}
421 f = {}
422 normcase = util.normcase
422 normcase = util.normcase
423 for name in self._dirs:
423 for name in self._dirs:
424 f[normcase(name)] = name
424 f[normcase(name)] = name
425 return f
425 return f
426
426
427 def hastrackeddir(self, d):
427 def hastrackeddir(self, d):
428 """
428 """
429 Returns True if the dirstate contains a tracked (not removed) file
429 Returns True if the dirstate contains a tracked (not removed) file
430 in this directory.
430 in this directory.
431 """
431 """
432 return d in self._dirs
432 return d in self._dirs
433
433
434 def hasdir(self, d):
434 def hasdir(self, d):
435 """
435 """
436 Returns True if the dirstate contains a file (tracked or removed)
436 Returns True if the dirstate contains a file (tracked or removed)
437 in this directory.
437 in this directory.
438 """
438 """
439 return d in self._alldirs
439 return d in self._alldirs
440
440
441 @propertycache
441 @propertycache
442 def _dirs(self):
442 def _dirs(self):
443 return pathutil.dirs(self._map, only_tracked=True)
443 return pathutil.dirs(self._map, only_tracked=True)
444
444
445 @propertycache
445 @propertycache
446 def _alldirs(self):
446 def _alldirs(self):
447 return pathutil.dirs(self._map)
447 return pathutil.dirs(self._map)
448
448
449 ### code related to manipulation of entries and copy-sources
449 ### code related to manipulation of entries and copy-sources
450
450
451 def reset_state(
451 def reset_state(
452 self,
452 self,
453 filename,
453 filename,
454 wc_tracked=False,
454 wc_tracked=False,
455 p1_tracked=False,
455 p1_tracked=False,
456 p2_info=False,
456 p2_info=False,
457 has_meaningful_mtime=True,
457 has_meaningful_mtime=True,
458 parentfiledata=None,
458 parentfiledata=None,
459 ):
459 ):
460 """Set a entry to a given state, diregarding all previous state
460 """Set a entry to a given state, diregarding all previous state
461
461
462 This is to be used by the part of the dirstate API dedicated to
462 This is to be used by the part of the dirstate API dedicated to
463 adjusting the dirstate after a update/merge.
463 adjusting the dirstate after a update/merge.
464
464
465 note: calling this might result to no entry existing at all if the
465 note: calling this might result to no entry existing at all if the
466 dirstate map does not see any point at having one for this file
466 dirstate map does not see any point at having one for this file
467 anymore.
467 anymore.
468 """
468 """
469 # copy information are now outdated
469 # copy information are now outdated
470 # (maybe new information should be in directly passed to this function)
470 # (maybe new information should be in directly passed to this function)
471 self.copymap.pop(filename, None)
471 self.copymap.pop(filename, None)
472
472
473 if not (p1_tracked or p2_info or wc_tracked):
473 if not (p1_tracked or p2_info or wc_tracked):
474 old_entry = self._map.get(filename)
474 old_entry = self._map.get(filename)
475 self._drop_entry(filename)
475 self._drop_entry(filename)
476 self._dirs_decr(filename, old_entry=old_entry)
476 self._dirs_decr(filename, old_entry=old_entry)
477 return
477 return
478
478
479 old_entry = self._map.get(filename)
479 old_entry = self._map.get(filename)
480 self._dirs_incr(filename, old_entry)
480 self._dirs_incr(filename, old_entry)
481 entry = DirstateItem(
481 entry = DirstateItem(
482 wc_tracked=wc_tracked,
482 wc_tracked=wc_tracked,
483 p1_tracked=p1_tracked,
483 p1_tracked=p1_tracked,
484 p2_info=p2_info,
484 p2_info=p2_info,
485 has_meaningful_mtime=has_meaningful_mtime,
485 has_meaningful_mtime=has_meaningful_mtime,
486 parentfiledata=parentfiledata,
486 parentfiledata=parentfiledata,
487 )
487 )
488 self._map[filename] = entry
488 self._map[filename] = entry
489
489
490 def set_tracked(self, filename):
490 def set_tracked(self, filename):
491 new = False
491 new = False
492 entry = self.get(filename)
492 entry = self.get(filename)
493 if entry is None:
493 if entry is None:
494 self._dirs_incr(filename)
494 self._dirs_incr(filename)
495 entry = DirstateItem(
495 entry = DirstateItem(
496 wc_tracked=True,
496 wc_tracked=True,
497 )
497 )
498
498
499 self._map[filename] = entry
499 self._map[filename] = entry
500 new = True
500 new = True
501 elif not entry.tracked:
501 elif not entry.tracked:
502 self._dirs_incr(filename, entry)
502 self._dirs_incr(filename, entry)
503 entry.set_tracked()
503 entry.set_tracked()
504 self._refresh_entry(filename, entry)
504 self._refresh_entry(filename, entry)
505 new = True
505 new = True
506 else:
506 else:
507 # XXX This is probably overkill for more case, but we need this to
507 # XXX This is probably overkill for more case, but we need this to
508 # fully replace the `normallookup` call with `set_tracked` one.
508 # fully replace the `normallookup` call with `set_tracked` one.
509 # Consider smoothing this in the future.
509 # Consider smoothing this in the future.
510 entry.set_possibly_dirty()
510 entry.set_possibly_dirty()
511 self._refresh_entry(filename, entry)
511 self._refresh_entry(filename, entry)
512 return new
512 return new
513
513
514 def set_untracked(self, f):
514 def set_untracked(self, f):
515 """Mark a file as no longer tracked in the dirstate map"""
515 """Mark a file as no longer tracked in the dirstate map"""
516 entry = self.get(f)
516 entry = self.get(f)
517 if entry is None:
517 if entry is None:
518 return False
518 return False
519 else:
519 else:
520 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
520 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
521 if not entry.p2_info:
521 if not entry.p2_info:
522 self.copymap.pop(f, None)
522 self.copymap.pop(f, None)
523 entry.set_untracked()
523 entry.set_untracked()
524 self._refresh_entry(f, entry)
524 self._refresh_entry(f, entry)
525 return True
525 return True
526
526
527 def set_clean(self, filename, mode, size, mtime):
527 def set_clean(self, filename, mode, size, mtime):
528 """mark a file as back to a clean state"""
528 """mark a file as back to a clean state"""
529 entry = self[filename]
529 entry = self[filename]
530 size = size & rangemask
530 size = size & rangemask
531 entry.set_clean(mode, size, mtime)
531 entry.set_clean(mode, size, mtime)
532 self._refresh_entry(filename, entry)
532 self._refresh_entry(filename, entry)
533 self.copymap.pop(filename, None)
533 self.copymap.pop(filename, None)
534
534
535 def set_possibly_dirty(self, filename):
535 def set_possibly_dirty(self, filename):
536 """record that the current state of the file on disk is unknown"""
536 """record that the current state of the file on disk is unknown"""
537 entry = self[filename]
537 entry = self[filename]
538 entry.set_possibly_dirty()
538 entry.set_possibly_dirty()
539 self._refresh_entry(filename, entry)
539 self._refresh_entry(filename, entry)
540
540
541 def _refresh_entry(self, f, entry):
541 def _refresh_entry(self, f, entry):
542 """record updated state of an entry"""
542 """record updated state of an entry"""
543 if not entry.any_tracked:
543 if not entry.any_tracked:
544 self._map.pop(f, None)
544 self._map.pop(f, None)
545
545
546 def _drop_entry(self, f):
546 def _drop_entry(self, f):
547 """remove any entry for file f
547 """remove any entry for file f
548
548
549 This should also drop associated copy information
549 This should also drop associated copy information
550
550
551 The fact we actually need to drop it is the responsability of the caller"""
551 The fact we actually need to drop it is the responsability of the caller"""
552 self._map.pop(f, None)
552 self._map.pop(f, None)
553 self.copymap.pop(f, None)
553 self.copymap.pop(f, None)
554
554
555
555
556 if rustmod is not None:
556 if rustmod is not None:
557
557
558 class dirstatemap(_dirstatemapcommon):
558 class dirstatemap(_dirstatemapcommon):
559
559
560 ### Core data storage and access
560 ### Core data storage and access
561
561
562 @propertycache
562 @propertycache
563 def _map(self):
563 def _map(self):
564 """
564 """
565 Fills the Dirstatemap when called.
565 Fills the Dirstatemap when called.
566 """
566 """
567 # ignore HG_PENDING because identity is used only for writing
567 # ignore HG_PENDING because identity is used only for writing
568 self._set_identity()
568 self._set_identity()
569
569
570 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
570 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
571 if self._use_dirstate_v2:
571 if self._use_dirstate_v2:
572 self.docket # load the data if needed
572 self.docket # load the data if needed
573 inode = (
574 self.identity.stat.st_ino
575 if self.identity is not None
576 and self.identity.stat is not None
577 else None
578 )
573 testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
579 testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
574 if not self.docket.uuid:
580 if not self.docket.uuid:
575 data = b''
581 data = b''
576 self._map = rustmod.DirstateMap.new_empty()
582 self._map = rustmod.DirstateMap.new_empty()
577 else:
583 else:
578 data = self._read_v2_data()
584 data = self._read_v2_data()
579 self._map = rustmod.DirstateMap.new_v2(
585 self._map = rustmod.DirstateMap.new_v2(
580 data,
586 data,
581 self.docket.data_size,
587 self.docket.data_size,
582 self.docket.tree_metadata,
588 self.docket.tree_metadata,
583 self.docket.uuid,
589 self.docket.uuid,
590 inode,
584 )
591 )
585 parents = self.docket.parents
592 parents = self.docket.parents
586 else:
593 else:
587 self._set_identity()
594 self._set_identity()
595 inode = (
596 self.identity.stat.st_ino
597 if self.identity is not None
598 and self.identity.stat is not None
599 else None
600 )
588 self._map, parents = rustmod.DirstateMap.new_v1(
601 self._map, parents = rustmod.DirstateMap.new_v1(
589 self._readdirstatefile()
602 self._readdirstatefile(), inode
590 )
603 )
591
604
592 if parents and not self._dirtyparents:
605 if parents and not self._dirtyparents:
593 self.setparents(*parents)
606 self.setparents(*parents)
594
607
595 self.__contains__ = self._map.__contains__
608 self.__contains__ = self._map.__contains__
596 self.__getitem__ = self._map.__getitem__
609 self.__getitem__ = self._map.__getitem__
597 self.get = self._map.get
610 self.get = self._map.get
598 return self._map
611 return self._map
599
612
600 @property
613 @property
601 def copymap(self):
614 def copymap(self):
602 return self._map.copymap()
615 return self._map.copymap()
603
616
604 def debug_iter(self, all):
617 def debug_iter(self, all):
605 """
618 """
606 Return an iterator of (filename, state, mode, size, mtime) tuples
619 Return an iterator of (filename, state, mode, size, mtime) tuples
607
620
608 `all`: also include with `state == b' '` dirstate tree nodes that
621 `all`: also include with `state == b' '` dirstate tree nodes that
609 don't have an associated `DirstateItem`.
622 don't have an associated `DirstateItem`.
610
623
611 """
624 """
612 return self._map.debug_iter(all)
625 return self._map.debug_iter(all)
613
626
614 def clear(self):
627 def clear(self):
615 self._map.clear()
628 self._map.clear()
616 self.setparents(
629 self.setparents(
617 self._nodeconstants.nullid, self._nodeconstants.nullid
630 self._nodeconstants.nullid, self._nodeconstants.nullid
618 )
631 )
619 util.clearcachedproperty(self, b"_dirs")
632 util.clearcachedproperty(self, b"_dirs")
620 util.clearcachedproperty(self, b"_alldirs")
633 util.clearcachedproperty(self, b"_alldirs")
621 util.clearcachedproperty(self, b"dirfoldmap")
634 util.clearcachedproperty(self, b"dirfoldmap")
622
635
623 def items(self):
636 def items(self):
624 return self._map.items()
637 return self._map.items()
625
638
626 # forward for python2,3 compat
639 # forward for python2,3 compat
627 iteritems = items
640 iteritems = items
628
641
629 def keys(self):
642 def keys(self):
630 return iter(self._map)
643 return iter(self._map)
631
644
632 ### reading/setting parents
645 ### reading/setting parents
633
646
634 def setparents(self, p1, p2, fold_p2=False):
647 def setparents(self, p1, p2, fold_p2=False):
635 self._parents = (p1, p2)
648 self._parents = (p1, p2)
636 self._dirtyparents = True
649 self._dirtyparents = True
637 copies = {}
650 copies = {}
638 if fold_p2:
651 if fold_p2:
639 copies = self._map.setparents_fixup()
652 copies = self._map.setparents_fixup()
640 return copies
653 return copies
641
654
642 ### disk interaction
655 ### disk interaction
643
656
644 @propertycache
657 @propertycache
645 def identity(self):
658 def identity(self):
646 self._map
659 self._map
647 return self.identity
660 return self.identity
648
661
649 def write(self, tr, st):
662 def write(self, tr, st):
650 if not self._use_dirstate_v2:
663 if not self._use_dirstate_v2:
651 p1, p2 = self.parents()
664 p1, p2 = self.parents()
652 packed = self._map.write_v1(p1, p2)
665 packed = self._map.write_v1(p1, p2)
653 st.write(packed)
666 st.write(packed)
654 st.close()
667 st.close()
655 self._dirtyparents = False
668 self._dirtyparents = False
656 return
669 return
657
670
658 # We can only append to an existing data file if there is one
671 # We can only append to an existing data file if there is one
659 write_mode = self._write_mode
672 write_mode = self._write_mode
660 if self.docket.uuid is None:
673 if self.docket.uuid is None:
661 write_mode = WRITE_MODE_FORCE_NEW
674 write_mode = WRITE_MODE_FORCE_NEW
662 packed, meta, append = self._map.write_v2(write_mode)
675 packed, meta, append = self._map.write_v2(write_mode)
663 if append:
676 if append:
664 docket = self.docket
677 docket = self.docket
665 data_filename = docket.data_filename()
678 data_filename = docket.data_filename()
666 with self._opener(data_filename, b'r+b') as fp:
679 with self._opener(data_filename, b'r+b') as fp:
667 fp.seek(docket.data_size)
680 fp.seek(docket.data_size)
668 assert fp.tell() == docket.data_size
681 assert fp.tell() == docket.data_size
669 written = fp.write(packed)
682 written = fp.write(packed)
670 if written is not None: # py2 may return None
683 if written is not None: # py2 may return None
671 assert written == len(packed), (written, len(packed))
684 assert written == len(packed), (written, len(packed))
672 docket.data_size += len(packed)
685 docket.data_size += len(packed)
673 docket.parents = self.parents()
686 docket.parents = self.parents()
674 docket.tree_metadata = meta
687 docket.tree_metadata = meta
675 st.write(docket.serialize())
688 st.write(docket.serialize())
676 st.close()
689 st.close()
677 else:
690 else:
678 self.write_v2_no_append(tr, st, meta, packed)
691 self.write_v2_no_append(tr, st, meta, packed)
679 # Reload from the newly-written file
692 # Reload from the newly-written file
680 util.clearcachedproperty(self, b"_map")
693 util.clearcachedproperty(self, b"_map")
681 self._dirtyparents = False
694 self._dirtyparents = False
682
695
683 ### code related to maintaining and accessing "extra" property
696 ### code related to maintaining and accessing "extra" property
684 # (e.g. "has_dir")
697 # (e.g. "has_dir")
685
698
686 @propertycache
699 @propertycache
687 def filefoldmap(self):
700 def filefoldmap(self):
688 """Returns a dictionary mapping normalized case paths to their
701 """Returns a dictionary mapping normalized case paths to their
689 non-normalized versions.
702 non-normalized versions.
690 """
703 """
691 return self._map.filefoldmapasdict()
704 return self._map.filefoldmapasdict()
692
705
693 def hastrackeddir(self, d):
706 def hastrackeddir(self, d):
694 return self._map.hastrackeddir(d)
707 return self._map.hastrackeddir(d)
695
708
696 def hasdir(self, d):
709 def hasdir(self, d):
697 return self._map.hasdir(d)
710 return self._map.hasdir(d)
698
711
699 @propertycache
712 @propertycache
700 def dirfoldmap(self):
713 def dirfoldmap(self):
701 f = {}
714 f = {}
702 normcase = util.normcase
715 normcase = util.normcase
703 for name in self._map.tracked_dirs():
716 for name in self._map.tracked_dirs():
704 f[normcase(name)] = name
717 f[normcase(name)] = name
705 return f
718 return f
706
719
707 ### code related to manipulation of entries and copy-sources
720 ### code related to manipulation of entries and copy-sources
708
721
709 def set_tracked(self, f):
722 def set_tracked(self, f):
710 return self._map.set_tracked(f)
723 return self._map.set_tracked(f)
711
724
712 def set_untracked(self, f):
725 def set_untracked(self, f):
713 return self._map.set_untracked(f)
726 return self._map.set_untracked(f)
714
727
715 def set_clean(self, filename, mode, size, mtime):
728 def set_clean(self, filename, mode, size, mtime):
716 self._map.set_clean(filename, mode, size, mtime)
729 self._map.set_clean(filename, mode, size, mtime)
717
730
718 def set_possibly_dirty(self, f):
731 def set_possibly_dirty(self, f):
719 self._map.set_possibly_dirty(f)
732 self._map.set_possibly_dirty(f)
720
733
721 def reset_state(
734 def reset_state(
722 self,
735 self,
723 filename,
736 filename,
724 wc_tracked=False,
737 wc_tracked=False,
725 p1_tracked=False,
738 p1_tracked=False,
726 p2_info=False,
739 p2_info=False,
727 has_meaningful_mtime=True,
740 has_meaningful_mtime=True,
728 parentfiledata=None,
741 parentfiledata=None,
729 ):
742 ):
730 return self._map.reset_state(
743 return self._map.reset_state(
731 filename,
744 filename,
732 wc_tracked,
745 wc_tracked,
733 p1_tracked,
746 p1_tracked,
734 p2_info,
747 p2_info,
735 has_meaningful_mtime,
748 has_meaningful_mtime,
736 parentfiledata,
749 parentfiledata,
737 )
750 )
@@ -1,1939 +1,1952 b''
1 use bytes_cast::BytesCast;
1 use bytes_cast::BytesCast;
2 use micro_timer::timed;
2 use micro_timer::timed;
3 use std::borrow::Cow;
3 use std::borrow::Cow;
4 use std::path::PathBuf;
4 use std::path::PathBuf;
5
5
6 use super::on_disk;
6 use super::on_disk;
7 use super::on_disk::DirstateV2ParseError;
7 use super::on_disk::DirstateV2ParseError;
8 use super::owning::OwningDirstateMap;
8 use super::owning::OwningDirstateMap;
9 use super::path_with_basename::WithBasename;
9 use super::path_with_basename::WithBasename;
10 use crate::dirstate::parsers::pack_entry;
10 use crate::dirstate::parsers::pack_entry;
11 use crate::dirstate::parsers::packed_entry_size;
11 use crate::dirstate::parsers::packed_entry_size;
12 use crate::dirstate::parsers::parse_dirstate_entries;
12 use crate::dirstate::parsers::parse_dirstate_entries;
13 use crate::dirstate::CopyMapIter;
13 use crate::dirstate::CopyMapIter;
14 use crate::dirstate::DirstateV2Data;
14 use crate::dirstate::DirstateV2Data;
15 use crate::dirstate::ParentFileData;
15 use crate::dirstate::ParentFileData;
16 use crate::dirstate::StateMapIter;
16 use crate::dirstate::StateMapIter;
17 use crate::dirstate::TruncatedTimestamp;
17 use crate::dirstate::TruncatedTimestamp;
18 use crate::matchers::Matcher;
18 use crate::matchers::Matcher;
19 use crate::utils::hg_path::{HgPath, HgPathBuf};
19 use crate::utils::hg_path::{HgPath, HgPathBuf};
20 use crate::DirstateEntry;
20 use crate::DirstateEntry;
21 use crate::DirstateError;
21 use crate::DirstateError;
22 use crate::DirstateMapError;
22 use crate::DirstateMapError;
23 use crate::DirstateParents;
23 use crate::DirstateParents;
24 use crate::DirstateStatus;
24 use crate::DirstateStatus;
25 use crate::FastHashbrownMap as FastHashMap;
25 use crate::FastHashbrownMap as FastHashMap;
26 use crate::PatternFileWarning;
26 use crate::PatternFileWarning;
27 use crate::StatusError;
27 use crate::StatusError;
28 use crate::StatusOptions;
28 use crate::StatusOptions;
29
29
30 /// Append to an existing data file if the amount of unreachable data (not used
30 /// Append to an existing data file if the amount of unreachable data (not used
31 /// anymore) is less than this fraction of the total amount of existing data.
31 /// anymore) is less than this fraction of the total amount of existing data.
32 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
32 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
33
33
34 #[derive(Debug, PartialEq, Eq)]
34 #[derive(Debug, PartialEq, Eq)]
35 /// Version of the on-disk format
35 /// Version of the on-disk format
36 pub enum DirstateVersion {
36 pub enum DirstateVersion {
37 V1,
37 V1,
38 V2,
38 V2,
39 }
39 }
40
40
41 #[derive(Debug, PartialEq, Eq)]
41 #[derive(Debug, PartialEq, Eq)]
42 pub enum DirstateMapWriteMode {
42 pub enum DirstateMapWriteMode {
43 Auto,
43 Auto,
44 ForceNewDataFile,
44 ForceNewDataFile,
45 ForceAppend,
45 ForceAppend,
46 }
46 }
47
47
48 #[derive(Debug)]
48 #[derive(Debug)]
49 pub struct DirstateMap<'on_disk> {
49 pub struct DirstateMap<'on_disk> {
50 /// Contents of the `.hg/dirstate` file
50 /// Contents of the `.hg/dirstate` file
51 pub(super) on_disk: &'on_disk [u8],
51 pub(super) on_disk: &'on_disk [u8],
52
52
53 pub(super) root: ChildNodes<'on_disk>,
53 pub(super) root: ChildNodes<'on_disk>,
54
54
55 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
55 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
56 pub(super) nodes_with_entry_count: u32,
56 pub(super) nodes_with_entry_count: u32,
57
57
58 /// Number of nodes anywhere in the tree that have
58 /// Number of nodes anywhere in the tree that have
59 /// `.copy_source.is_some()`.
59 /// `.copy_source.is_some()`.
60 pub(super) nodes_with_copy_source_count: u32,
60 pub(super) nodes_with_copy_source_count: u32,
61
61
62 /// See on_disk::Header
62 /// See on_disk::Header
63 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
63 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
64
64
65 /// How many bytes of `on_disk` are not used anymore
65 /// How many bytes of `on_disk` are not used anymore
66 pub(super) unreachable_bytes: u32,
66 pub(super) unreachable_bytes: u32,
67
67
68 /// Size of the data used to first load this `DirstateMap`. Used in case
68 /// Size of the data used to first load this `DirstateMap`. Used in case
69 /// we need to write some new metadata, but no new data on disk,
69 /// we need to write some new metadata, but no new data on disk,
70 /// as well as to detect writes that have happened in another process
70 /// as well as to detect writes that have happened in another process
71 /// since first read.
71 /// since first read.
72 pub(super) old_data_size: usize,
72 pub(super) old_data_size: usize,
73
73
74 /// UUID used when first loading this `DirstateMap`. Used to check if
74 /// UUID used when first loading this `DirstateMap`. Used to check if
75 /// the UUID has been changed by another process since first read.
75 /// the UUID has been changed by another process since first read.
76 /// Can be `None` if using dirstate v1 or if it's a brand new dirstate.
76 /// Can be `None` if using dirstate v1 or if it's a brand new dirstate.
77 pub(super) old_uuid: Option<Vec<u8>>,
77 pub(super) old_uuid: Option<Vec<u8>>,
78
78
79 /// Identity of the dirstate file (for dirstate-v1) or the docket file
80 /// (v2). Used to detect if the file has changed from another process.
81 /// Since it's always written atomically, we can compare the inode to
82 /// check the file identity.
83 ///
84 /// TODO On non-Unix systems, something like hashing is a possibility?
85 pub(super) identity: Option<u64>,
86
79 pub(super) dirstate_version: DirstateVersion,
87 pub(super) dirstate_version: DirstateVersion,
80
88
81 /// Controlled by config option `devel.dirstate.v2.data_update_mode`
89 /// Controlled by config option `devel.dirstate.v2.data_update_mode`
82 pub(super) write_mode: DirstateMapWriteMode,
90 pub(super) write_mode: DirstateMapWriteMode,
83 }
91 }
84
92
85 /// Using a plain `HgPathBuf` of the full path from the repository root as a
93 /// Using a plain `HgPathBuf` of the full path from the repository root as a
86 /// map key would also work: all paths in a given map have the same parent
94 /// map key would also work: all paths in a given map have the same parent
87 /// path, so comparing full paths gives the same result as comparing base
95 /// path, so comparing full paths gives the same result as comparing base
88 /// names. However `HashMap` would waste time always re-hashing the same
96 /// names. However `HashMap` would waste time always re-hashing the same
89 /// string prefix.
97 /// string prefix.
90 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
98 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
91
99
92 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
100 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
93 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
101 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
94 #[derive(Debug)]
102 #[derive(Debug)]
95 pub(super) enum BorrowedPath<'tree, 'on_disk> {
103 pub(super) enum BorrowedPath<'tree, 'on_disk> {
96 InMemory(&'tree HgPathBuf),
104 InMemory(&'tree HgPathBuf),
97 OnDisk(&'on_disk HgPath),
105 OnDisk(&'on_disk HgPath),
98 }
106 }
99
107
100 #[derive(Debug)]
108 #[derive(Debug)]
101 pub(super) enum ChildNodes<'on_disk> {
109 pub(super) enum ChildNodes<'on_disk> {
102 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
110 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
103 OnDisk(&'on_disk [on_disk::Node]),
111 OnDisk(&'on_disk [on_disk::Node]),
104 }
112 }
105
113
106 #[derive(Debug)]
114 #[derive(Debug)]
107 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
115 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
108 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
116 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
109 OnDisk(&'on_disk [on_disk::Node]),
117 OnDisk(&'on_disk [on_disk::Node]),
110 }
118 }
111
119
112 #[derive(Debug)]
120 #[derive(Debug)]
113 pub(super) enum NodeRef<'tree, 'on_disk> {
121 pub(super) enum NodeRef<'tree, 'on_disk> {
114 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
122 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
115 OnDisk(&'on_disk on_disk::Node),
123 OnDisk(&'on_disk on_disk::Node),
116 }
124 }
117
125
118 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
126 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
119 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
127 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
120 match *self {
128 match *self {
121 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
129 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
122 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
130 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
123 }
131 }
124 }
132 }
125 }
133 }
126
134
127 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
135 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
128 type Target = HgPath;
136 type Target = HgPath;
129
137
130 fn deref(&self) -> &HgPath {
138 fn deref(&self) -> &HgPath {
131 match *self {
139 match *self {
132 BorrowedPath::InMemory(in_memory) => in_memory,
140 BorrowedPath::InMemory(in_memory) => in_memory,
133 BorrowedPath::OnDisk(on_disk) => on_disk,
141 BorrowedPath::OnDisk(on_disk) => on_disk,
134 }
142 }
135 }
143 }
136 }
144 }
137
145
138 impl Default for ChildNodes<'_> {
146 impl Default for ChildNodes<'_> {
139 fn default() -> Self {
147 fn default() -> Self {
140 ChildNodes::InMemory(Default::default())
148 ChildNodes::InMemory(Default::default())
141 }
149 }
142 }
150 }
143
151
144 impl<'on_disk> ChildNodes<'on_disk> {
152 impl<'on_disk> ChildNodes<'on_disk> {
145 pub(super) fn as_ref<'tree>(
153 pub(super) fn as_ref<'tree>(
146 &'tree self,
154 &'tree self,
147 ) -> ChildNodesRef<'tree, 'on_disk> {
155 ) -> ChildNodesRef<'tree, 'on_disk> {
148 match self {
156 match self {
149 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
157 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
150 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
158 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
151 }
159 }
152 }
160 }
153
161
154 pub(super) fn is_empty(&self) -> bool {
162 pub(super) fn is_empty(&self) -> bool {
155 match self {
163 match self {
156 ChildNodes::InMemory(nodes) => nodes.is_empty(),
164 ChildNodes::InMemory(nodes) => nodes.is_empty(),
157 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
165 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
158 }
166 }
159 }
167 }
160
168
161 fn make_mut(
169 fn make_mut(
162 &mut self,
170 &mut self,
163 on_disk: &'on_disk [u8],
171 on_disk: &'on_disk [u8],
164 unreachable_bytes: &mut u32,
172 unreachable_bytes: &mut u32,
165 ) -> Result<
173 ) -> Result<
166 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
174 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
167 DirstateV2ParseError,
175 DirstateV2ParseError,
168 > {
176 > {
169 match self {
177 match self {
170 ChildNodes::InMemory(nodes) => Ok(nodes),
178 ChildNodes::InMemory(nodes) => Ok(nodes),
171 ChildNodes::OnDisk(nodes) => {
179 ChildNodes::OnDisk(nodes) => {
172 *unreachable_bytes +=
180 *unreachable_bytes +=
173 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
181 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
174 let nodes = nodes
182 let nodes = nodes
175 .iter()
183 .iter()
176 .map(|node| {
184 .map(|node| {
177 Ok((
185 Ok((
178 node.path(on_disk)?,
186 node.path(on_disk)?,
179 node.to_in_memory_node(on_disk)?,
187 node.to_in_memory_node(on_disk)?,
180 ))
188 ))
181 })
189 })
182 .collect::<Result<_, _>>()?;
190 .collect::<Result<_, _>>()?;
183 *self = ChildNodes::InMemory(nodes);
191 *self = ChildNodes::InMemory(nodes);
184 match self {
192 match self {
185 ChildNodes::InMemory(nodes) => Ok(nodes),
193 ChildNodes::InMemory(nodes) => Ok(nodes),
186 ChildNodes::OnDisk(_) => unreachable!(),
194 ChildNodes::OnDisk(_) => unreachable!(),
187 }
195 }
188 }
196 }
189 }
197 }
190 }
198 }
191 }
199 }
192
200
193 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
201 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
194 pub(super) fn get(
202 pub(super) fn get(
195 &self,
203 &self,
196 base_name: &HgPath,
204 base_name: &HgPath,
197 on_disk: &'on_disk [u8],
205 on_disk: &'on_disk [u8],
198 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
206 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
199 match self {
207 match self {
200 ChildNodesRef::InMemory(nodes) => Ok(nodes
208 ChildNodesRef::InMemory(nodes) => Ok(nodes
201 .get_key_value(base_name)
209 .get_key_value(base_name)
202 .map(|(k, v)| NodeRef::InMemory(k, v))),
210 .map(|(k, v)| NodeRef::InMemory(k, v))),
203 ChildNodesRef::OnDisk(nodes) => {
211 ChildNodesRef::OnDisk(nodes) => {
204 let mut parse_result = Ok(());
212 let mut parse_result = Ok(());
205 let search_result = nodes.binary_search_by(|node| {
213 let search_result = nodes.binary_search_by(|node| {
206 match node.base_name(on_disk) {
214 match node.base_name(on_disk) {
207 Ok(node_base_name) => node_base_name.cmp(base_name),
215 Ok(node_base_name) => node_base_name.cmp(base_name),
208 Err(e) => {
216 Err(e) => {
209 parse_result = Err(e);
217 parse_result = Err(e);
210 // Dummy comparison result, `search_result` won’t
218 // Dummy comparison result, `search_result` won’t
211 // be used since `parse_result` is an error
219 // be used since `parse_result` is an error
212 std::cmp::Ordering::Equal
220 std::cmp::Ordering::Equal
213 }
221 }
214 }
222 }
215 });
223 });
216 parse_result.map(|()| {
224 parse_result.map(|()| {
217 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
225 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
218 })
226 })
219 }
227 }
220 }
228 }
221 }
229 }
222
230
223 /// Iterate in undefined order
231 /// Iterate in undefined order
224 pub(super) fn iter(
232 pub(super) fn iter(
225 &self,
233 &self,
226 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
234 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
227 match self {
235 match self {
228 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
236 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
229 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
237 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
230 ),
238 ),
231 ChildNodesRef::OnDisk(nodes) => {
239 ChildNodesRef::OnDisk(nodes) => {
232 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
240 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
233 }
241 }
234 }
242 }
235 }
243 }
236
244
237 /// Iterate in parallel in undefined order
245 /// Iterate in parallel in undefined order
238 pub(super) fn par_iter(
246 pub(super) fn par_iter(
239 &self,
247 &self,
240 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
248 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
241 {
249 {
242 use rayon::prelude::*;
250 use rayon::prelude::*;
243 match self {
251 match self {
244 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
252 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
245 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
253 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
246 ),
254 ),
247 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
255 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
248 nodes.par_iter().map(NodeRef::OnDisk),
256 nodes.par_iter().map(NodeRef::OnDisk),
249 ),
257 ),
250 }
258 }
251 }
259 }
252
260
253 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
261 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
254 match self {
262 match self {
255 ChildNodesRef::InMemory(nodes) => {
263 ChildNodesRef::InMemory(nodes) => {
256 let mut vec: Vec<_> = nodes
264 let mut vec: Vec<_> = nodes
257 .iter()
265 .iter()
258 .map(|(k, v)| NodeRef::InMemory(k, v))
266 .map(|(k, v)| NodeRef::InMemory(k, v))
259 .collect();
267 .collect();
260 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
268 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
261 match node {
269 match node {
262 NodeRef::InMemory(path, _node) => path.base_name(),
270 NodeRef::InMemory(path, _node) => path.base_name(),
263 NodeRef::OnDisk(_) => unreachable!(),
271 NodeRef::OnDisk(_) => unreachable!(),
264 }
272 }
265 }
273 }
266 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
274 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
267 // value: https://github.com/rust-lang/rust/issues/34162
275 // value: https://github.com/rust-lang/rust/issues/34162
268 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
276 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
269 vec
277 vec
270 }
278 }
271 ChildNodesRef::OnDisk(nodes) => {
279 ChildNodesRef::OnDisk(nodes) => {
272 // Nodes on disk are already sorted
280 // Nodes on disk are already sorted
273 nodes.iter().map(NodeRef::OnDisk).collect()
281 nodes.iter().map(NodeRef::OnDisk).collect()
274 }
282 }
275 }
283 }
276 }
284 }
277 }
285 }
278
286
279 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
287 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
280 pub(super) fn full_path(
288 pub(super) fn full_path(
281 &self,
289 &self,
282 on_disk: &'on_disk [u8],
290 on_disk: &'on_disk [u8],
283 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
291 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
284 match self {
292 match self {
285 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
293 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
286 NodeRef::OnDisk(node) => node.full_path(on_disk),
294 NodeRef::OnDisk(node) => node.full_path(on_disk),
287 }
295 }
288 }
296 }
289
297
290 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
298 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
291 /// HgPath>` detached from `'tree`
299 /// HgPath>` detached from `'tree`
292 pub(super) fn full_path_borrowed(
300 pub(super) fn full_path_borrowed(
293 &self,
301 &self,
294 on_disk: &'on_disk [u8],
302 on_disk: &'on_disk [u8],
295 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
303 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
296 match self {
304 match self {
297 NodeRef::InMemory(path, _node) => match path.full_path() {
305 NodeRef::InMemory(path, _node) => match path.full_path() {
298 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
306 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
299 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
307 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
300 },
308 },
301 NodeRef::OnDisk(node) => {
309 NodeRef::OnDisk(node) => {
302 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
310 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
303 }
311 }
304 }
312 }
305 }
313 }
306
314
307 pub(super) fn base_name(
315 pub(super) fn base_name(
308 &self,
316 &self,
309 on_disk: &'on_disk [u8],
317 on_disk: &'on_disk [u8],
310 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
318 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
311 match self {
319 match self {
312 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
320 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
313 NodeRef::OnDisk(node) => node.base_name(on_disk),
321 NodeRef::OnDisk(node) => node.base_name(on_disk),
314 }
322 }
315 }
323 }
316
324
317 pub(super) fn children(
325 pub(super) fn children(
318 &self,
326 &self,
319 on_disk: &'on_disk [u8],
327 on_disk: &'on_disk [u8],
320 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
328 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
321 match self {
329 match self {
322 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
330 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
323 NodeRef::OnDisk(node) => {
331 NodeRef::OnDisk(node) => {
324 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
332 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
325 }
333 }
326 }
334 }
327 }
335 }
328
336
329 pub(super) fn has_copy_source(&self) -> bool {
337 pub(super) fn has_copy_source(&self) -> bool {
330 match self {
338 match self {
331 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
339 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
332 NodeRef::OnDisk(node) => node.has_copy_source(),
340 NodeRef::OnDisk(node) => node.has_copy_source(),
333 }
341 }
334 }
342 }
335
343
336 pub(super) fn copy_source(
344 pub(super) fn copy_source(
337 &self,
345 &self,
338 on_disk: &'on_disk [u8],
346 on_disk: &'on_disk [u8],
339 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
347 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
340 match self {
348 match self {
341 NodeRef::InMemory(_path, node) => {
349 NodeRef::InMemory(_path, node) => {
342 Ok(node.copy_source.as_ref().map(|s| &**s))
350 Ok(node.copy_source.as_ref().map(|s| &**s))
343 }
351 }
344 NodeRef::OnDisk(node) => node.copy_source(on_disk),
352 NodeRef::OnDisk(node) => node.copy_source(on_disk),
345 }
353 }
346 }
354 }
347 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
355 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
348 /// HgPath>` detached from `'tree`
356 /// HgPath>` detached from `'tree`
349 pub(super) fn copy_source_borrowed(
357 pub(super) fn copy_source_borrowed(
350 &self,
358 &self,
351 on_disk: &'on_disk [u8],
359 on_disk: &'on_disk [u8],
352 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
360 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
353 {
361 {
354 Ok(match self {
362 Ok(match self {
355 NodeRef::InMemory(_path, node) => {
363 NodeRef::InMemory(_path, node) => {
356 node.copy_source.as_ref().map(|source| match source {
364 node.copy_source.as_ref().map(|source| match source {
357 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
365 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
358 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
366 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
359 })
367 })
360 }
368 }
361 NodeRef::OnDisk(node) => node
369 NodeRef::OnDisk(node) => node
362 .copy_source(on_disk)?
370 .copy_source(on_disk)?
363 .map(|source| BorrowedPath::OnDisk(source)),
371 .map(|source| BorrowedPath::OnDisk(source)),
364 })
372 })
365 }
373 }
366
374
367 pub(super) fn entry(
375 pub(super) fn entry(
368 &self,
376 &self,
369 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
377 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
370 match self {
378 match self {
371 NodeRef::InMemory(_path, node) => {
379 NodeRef::InMemory(_path, node) => {
372 Ok(node.data.as_entry().copied())
380 Ok(node.data.as_entry().copied())
373 }
381 }
374 NodeRef::OnDisk(node) => node.entry(),
382 NodeRef::OnDisk(node) => node.entry(),
375 }
383 }
376 }
384 }
377
385
378 pub(super) fn cached_directory_mtime(
386 pub(super) fn cached_directory_mtime(
379 &self,
387 &self,
380 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
388 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
381 match self {
389 match self {
382 NodeRef::InMemory(_path, node) => Ok(match node.data {
390 NodeRef::InMemory(_path, node) => Ok(match node.data {
383 NodeData::CachedDirectory { mtime } => Some(mtime),
391 NodeData::CachedDirectory { mtime } => Some(mtime),
384 _ => None,
392 _ => None,
385 }),
393 }),
386 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
394 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
387 }
395 }
388 }
396 }
389
397
390 pub(super) fn descendants_with_entry_count(&self) -> u32 {
398 pub(super) fn descendants_with_entry_count(&self) -> u32 {
391 match self {
399 match self {
392 NodeRef::InMemory(_path, node) => {
400 NodeRef::InMemory(_path, node) => {
393 node.descendants_with_entry_count
401 node.descendants_with_entry_count
394 }
402 }
395 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
403 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
396 }
404 }
397 }
405 }
398
406
399 pub(super) fn tracked_descendants_count(&self) -> u32 {
407 pub(super) fn tracked_descendants_count(&self) -> u32 {
400 match self {
408 match self {
401 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
409 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
402 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
410 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
403 }
411 }
404 }
412 }
405 }
413 }
406
414
407 /// Represents a file or a directory
415 /// Represents a file or a directory
408 #[derive(Default, Debug)]
416 #[derive(Default, Debug)]
409 pub(super) struct Node<'on_disk> {
417 pub(super) struct Node<'on_disk> {
410 pub(super) data: NodeData,
418 pub(super) data: NodeData,
411
419
412 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
420 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
413
421
414 pub(super) children: ChildNodes<'on_disk>,
422 pub(super) children: ChildNodes<'on_disk>,
415
423
416 /// How many (non-inclusive) descendants of this node have an entry.
424 /// How many (non-inclusive) descendants of this node have an entry.
417 pub(super) descendants_with_entry_count: u32,
425 pub(super) descendants_with_entry_count: u32,
418
426
419 /// How many (non-inclusive) descendants of this node have an entry whose
427 /// How many (non-inclusive) descendants of this node have an entry whose
420 /// state is "tracked".
428 /// state is "tracked".
421 pub(super) tracked_descendants_count: u32,
429 pub(super) tracked_descendants_count: u32,
422 }
430 }
423
431
424 #[derive(Debug)]
432 #[derive(Debug)]
425 pub(super) enum NodeData {
433 pub(super) enum NodeData {
426 Entry(DirstateEntry),
434 Entry(DirstateEntry),
427 CachedDirectory { mtime: TruncatedTimestamp },
435 CachedDirectory { mtime: TruncatedTimestamp },
428 None,
436 None,
429 }
437 }
430
438
431 impl Default for NodeData {
439 impl Default for NodeData {
432 fn default() -> Self {
440 fn default() -> Self {
433 NodeData::None
441 NodeData::None
434 }
442 }
435 }
443 }
436
444
437 impl NodeData {
445 impl NodeData {
438 fn has_entry(&self) -> bool {
446 fn has_entry(&self) -> bool {
439 match self {
447 match self {
440 NodeData::Entry(_) => true,
448 NodeData::Entry(_) => true,
441 _ => false,
449 _ => false,
442 }
450 }
443 }
451 }
444
452
445 fn as_entry(&self) -> Option<&DirstateEntry> {
453 fn as_entry(&self) -> Option<&DirstateEntry> {
446 match self {
454 match self {
447 NodeData::Entry(entry) => Some(entry),
455 NodeData::Entry(entry) => Some(entry),
448 _ => None,
456 _ => None,
449 }
457 }
450 }
458 }
451
459
452 fn as_entry_mut(&mut self) -> Option<&mut DirstateEntry> {
460 fn as_entry_mut(&mut self) -> Option<&mut DirstateEntry> {
453 match self {
461 match self {
454 NodeData::Entry(entry) => Some(entry),
462 NodeData::Entry(entry) => Some(entry),
455 _ => None,
463 _ => None,
456 }
464 }
457 }
465 }
458 }
466 }
459
467
460 impl<'on_disk> DirstateMap<'on_disk> {
468 impl<'on_disk> DirstateMap<'on_disk> {
461 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
469 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
462 Self {
470 Self {
463 on_disk,
471 on_disk,
464 root: ChildNodes::default(),
472 root: ChildNodes::default(),
465 nodes_with_entry_count: 0,
473 nodes_with_entry_count: 0,
466 nodes_with_copy_source_count: 0,
474 nodes_with_copy_source_count: 0,
467 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
475 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
468 unreachable_bytes: 0,
476 unreachable_bytes: 0,
469 old_data_size: 0,
477 old_data_size: 0,
470 old_uuid: None,
478 old_uuid: None,
479 identity: None,
471 dirstate_version: DirstateVersion::V1,
480 dirstate_version: DirstateVersion::V1,
472 write_mode: DirstateMapWriteMode::Auto,
481 write_mode: DirstateMapWriteMode::Auto,
473 }
482 }
474 }
483 }
475
484
476 #[timed]
485 #[timed]
477 pub fn new_v2(
486 pub fn new_v2(
478 on_disk: &'on_disk [u8],
487 on_disk: &'on_disk [u8],
479 data_size: usize,
488 data_size: usize,
480 metadata: &[u8],
489 metadata: &[u8],
481 uuid: Vec<u8>,
490 uuid: Vec<u8>,
491 identity: Option<u64>,
482 ) -> Result<Self, DirstateError> {
492 ) -> Result<Self, DirstateError> {
483 if let Some(data) = on_disk.get(..data_size) {
493 if let Some(data) = on_disk.get(..data_size) {
484 Ok(on_disk::read(data, metadata, uuid)?)
494 Ok(on_disk::read(data, metadata, uuid, identity)?)
485 } else {
495 } else {
486 Err(DirstateV2ParseError::new("not enough bytes on disk").into())
496 Err(DirstateV2ParseError::new("not enough bytes on disk").into())
487 }
497 }
488 }
498 }
489
499
490 #[timed]
500 #[timed]
491 pub fn new_v1(
501 pub fn new_v1(
492 on_disk: &'on_disk [u8],
502 on_disk: &'on_disk [u8],
503 identity: Option<u64>,
493 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
504 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
494 let mut map = Self::empty(on_disk);
505 let mut map = Self::empty(on_disk);
495 if map.on_disk.is_empty() {
506 if map.on_disk.is_empty() {
496 return Ok((map, None));
507 return Ok((map, None));
497 }
508 }
498
509
499 let parents = parse_dirstate_entries(
510 let parents = parse_dirstate_entries(
500 map.on_disk,
511 map.on_disk,
501 |path, entry, copy_source| {
512 |path, entry, copy_source| {
502 let tracked = entry.tracked();
513 let tracked = entry.tracked();
503 let node = Self::get_or_insert_node_inner(
514 let node = Self::get_or_insert_node_inner(
504 map.on_disk,
515 map.on_disk,
505 &mut map.unreachable_bytes,
516 &mut map.unreachable_bytes,
506 &mut map.root,
517 &mut map.root,
507 path,
518 path,
508 WithBasename::to_cow_borrowed,
519 WithBasename::to_cow_borrowed,
509 |ancestor| {
520 |ancestor| {
510 if tracked {
521 if tracked {
511 ancestor.tracked_descendants_count += 1
522 ancestor.tracked_descendants_count += 1
512 }
523 }
513 ancestor.descendants_with_entry_count += 1
524 ancestor.descendants_with_entry_count += 1
514 },
525 },
515 )?;
526 )?;
516 assert!(
527 assert!(
517 !node.data.has_entry(),
528 !node.data.has_entry(),
518 "duplicate dirstate entry in read"
529 "duplicate dirstate entry in read"
519 );
530 );
520 assert!(
531 assert!(
521 node.copy_source.is_none(),
532 node.copy_source.is_none(),
522 "duplicate dirstate entry in read"
533 "duplicate dirstate entry in read"
523 );
534 );
524 node.data = NodeData::Entry(*entry);
535 node.data = NodeData::Entry(*entry);
525 node.copy_source = copy_source.map(Cow::Borrowed);
536 node.copy_source = copy_source.map(Cow::Borrowed);
526 map.nodes_with_entry_count += 1;
537 map.nodes_with_entry_count += 1;
527 if copy_source.is_some() {
538 if copy_source.is_some() {
528 map.nodes_with_copy_source_count += 1
539 map.nodes_with_copy_source_count += 1
529 }
540 }
530 Ok(())
541 Ok(())
531 },
542 },
532 )?;
543 )?;
533 let parents = Some(parents.clone());
544 let parents = Some(parents.clone());
545 map.identity = identity;
534
546
535 Ok((map, parents))
547 Ok((map, parents))
536 }
548 }
537
549
538 /// Assuming dirstate-v2 format, returns whether the next write should
550 /// Assuming dirstate-v2 format, returns whether the next write should
539 /// append to the existing data file that contains `self.on_disk` (true),
551 /// append to the existing data file that contains `self.on_disk` (true),
540 /// or create a new data file from scratch (false).
552 /// or create a new data file from scratch (false).
541 pub(super) fn write_should_append(&self) -> bool {
553 pub(super) fn write_should_append(&self) -> bool {
542 match self.write_mode {
554 match self.write_mode {
543 DirstateMapWriteMode::ForceAppend => true,
555 DirstateMapWriteMode::ForceAppend => true,
544 DirstateMapWriteMode::ForceNewDataFile => false,
556 DirstateMapWriteMode::ForceNewDataFile => false,
545 DirstateMapWriteMode::Auto => {
557 DirstateMapWriteMode::Auto => {
546 let ratio =
558 let ratio =
547 self.unreachable_bytes as f32 / self.on_disk.len() as f32;
559 self.unreachable_bytes as f32 / self.on_disk.len() as f32;
548 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
560 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
549 }
561 }
550 }
562 }
551 }
563 }
552
564
553 fn get_node<'tree>(
565 fn get_node<'tree>(
554 &'tree self,
566 &'tree self,
555 path: &HgPath,
567 path: &HgPath,
556 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
568 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
557 let mut children = self.root.as_ref();
569 let mut children = self.root.as_ref();
558 let mut components = path.components();
570 let mut components = path.components();
559 let mut component =
571 let mut component =
560 components.next().expect("expected at least one components");
572 components.next().expect("expected at least one components");
561 loop {
573 loop {
562 if let Some(child) = children.get(component, self.on_disk)? {
574 if let Some(child) = children.get(component, self.on_disk)? {
563 if let Some(next_component) = components.next() {
575 if let Some(next_component) = components.next() {
564 component = next_component;
576 component = next_component;
565 children = child.children(self.on_disk)?;
577 children = child.children(self.on_disk)?;
566 } else {
578 } else {
567 return Ok(Some(child));
579 return Ok(Some(child));
568 }
580 }
569 } else {
581 } else {
570 return Ok(None);
582 return Ok(None);
571 }
583 }
572 }
584 }
573 }
585 }
574
586
575 /// Returns a mutable reference to the node at `path` if it exists
587 /// Returns a mutable reference to the node at `path` if it exists
576 ///
588 ///
577 /// `each_ancestor` is a callback that is called for each ancestor node
589 /// `each_ancestor` is a callback that is called for each ancestor node
578 /// when descending the tree. It is used to keep the different counters
590 /// when descending the tree. It is used to keep the different counters
579 /// of the `DirstateMap` up-to-date.
591 /// of the `DirstateMap` up-to-date.
580 fn get_node_mut<'tree>(
592 fn get_node_mut<'tree>(
581 &'tree mut self,
593 &'tree mut self,
582 path: &HgPath,
594 path: &HgPath,
583 each_ancestor: impl FnMut(&mut Node),
595 each_ancestor: impl FnMut(&mut Node),
584 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
596 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
585 Self::get_node_mut_inner(
597 Self::get_node_mut_inner(
586 self.on_disk,
598 self.on_disk,
587 &mut self.unreachable_bytes,
599 &mut self.unreachable_bytes,
588 &mut self.root,
600 &mut self.root,
589 path,
601 path,
590 each_ancestor,
602 each_ancestor,
591 )
603 )
592 }
604 }
593
605
594 /// Lower-level version of `get_node_mut`.
606 /// Lower-level version of `get_node_mut`.
595 ///
607 ///
596 /// This takes `root` instead of `&mut self` so that callers can mutate
608 /// This takes `root` instead of `&mut self` so that callers can mutate
597 /// other fields while the returned borrow is still valid.
609 /// other fields while the returned borrow is still valid.
598 ///
610 ///
599 /// `each_ancestor` is a callback that is called for each ancestor node
611 /// `each_ancestor` is a callback that is called for each ancestor node
600 /// when descending the tree. It is used to keep the different counters
612 /// when descending the tree. It is used to keep the different counters
601 /// of the `DirstateMap` up-to-date.
613 /// of the `DirstateMap` up-to-date.
602 fn get_node_mut_inner<'tree>(
614 fn get_node_mut_inner<'tree>(
603 on_disk: &'on_disk [u8],
615 on_disk: &'on_disk [u8],
604 unreachable_bytes: &mut u32,
616 unreachable_bytes: &mut u32,
605 root: &'tree mut ChildNodes<'on_disk>,
617 root: &'tree mut ChildNodes<'on_disk>,
606 path: &HgPath,
618 path: &HgPath,
607 mut each_ancestor: impl FnMut(&mut Node),
619 mut each_ancestor: impl FnMut(&mut Node),
608 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
620 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
609 let mut children = root;
621 let mut children = root;
610 let mut components = path.components();
622 let mut components = path.components();
611 let mut component =
623 let mut component =
612 components.next().expect("expected at least one components");
624 components.next().expect("expected at least one components");
613 loop {
625 loop {
614 if let Some(child) = children
626 if let Some(child) = children
615 .make_mut(on_disk, unreachable_bytes)?
627 .make_mut(on_disk, unreachable_bytes)?
616 .get_mut(component)
628 .get_mut(component)
617 {
629 {
618 if let Some(next_component) = components.next() {
630 if let Some(next_component) = components.next() {
619 each_ancestor(child);
631 each_ancestor(child);
620 component = next_component;
632 component = next_component;
621 children = &mut child.children;
633 children = &mut child.children;
622 } else {
634 } else {
623 return Ok(Some(child));
635 return Ok(Some(child));
624 }
636 }
625 } else {
637 } else {
626 return Ok(None);
638 return Ok(None);
627 }
639 }
628 }
640 }
629 }
641 }
630
642
631 /// Get a mutable reference to the node at `path`, creating it if it does
643 /// Get a mutable reference to the node at `path`, creating it if it does
632 /// not exist.
644 /// not exist.
633 ///
645 ///
634 /// `each_ancestor` is a callback that is called for each ancestor node
646 /// `each_ancestor` is a callback that is called for each ancestor node
635 /// when descending the tree. It is used to keep the different counters
647 /// when descending the tree. It is used to keep the different counters
636 /// of the `DirstateMap` up-to-date.
648 /// of the `DirstateMap` up-to-date.
637 fn get_or_insert_node<'tree, 'path>(
649 fn get_or_insert_node<'tree, 'path>(
638 &'tree mut self,
650 &'tree mut self,
639 path: &'path HgPath,
651 path: &'path HgPath,
640 each_ancestor: impl FnMut(&mut Node),
652 each_ancestor: impl FnMut(&mut Node),
641 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
653 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
642 Self::get_or_insert_node_inner(
654 Self::get_or_insert_node_inner(
643 self.on_disk,
655 self.on_disk,
644 &mut self.unreachable_bytes,
656 &mut self.unreachable_bytes,
645 &mut self.root,
657 &mut self.root,
646 path,
658 path,
647 WithBasename::to_cow_owned,
659 WithBasename::to_cow_owned,
648 each_ancestor,
660 each_ancestor,
649 )
661 )
650 }
662 }
651
663
652 /// Lower-level version of `get_or_insert_node_inner`, which is used when
664 /// Lower-level version of `get_or_insert_node_inner`, which is used when
653 /// parsing disk data to remove allocations for new nodes.
665 /// parsing disk data to remove allocations for new nodes.
654 fn get_or_insert_node_inner<'tree, 'path>(
666 fn get_or_insert_node_inner<'tree, 'path>(
655 on_disk: &'on_disk [u8],
667 on_disk: &'on_disk [u8],
656 unreachable_bytes: &mut u32,
668 unreachable_bytes: &mut u32,
657 root: &'tree mut ChildNodes<'on_disk>,
669 root: &'tree mut ChildNodes<'on_disk>,
658 path: &'path HgPath,
670 path: &'path HgPath,
659 to_cow: impl Fn(
671 to_cow: impl Fn(
660 WithBasename<&'path HgPath>,
672 WithBasename<&'path HgPath>,
661 ) -> WithBasename<Cow<'on_disk, HgPath>>,
673 ) -> WithBasename<Cow<'on_disk, HgPath>>,
662 mut each_ancestor: impl FnMut(&mut Node),
674 mut each_ancestor: impl FnMut(&mut Node),
663 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
675 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
664 let mut child_nodes = root;
676 let mut child_nodes = root;
665 let mut inclusive_ancestor_paths =
677 let mut inclusive_ancestor_paths =
666 WithBasename::inclusive_ancestors_of(path);
678 WithBasename::inclusive_ancestors_of(path);
667 let mut ancestor_path = inclusive_ancestor_paths
679 let mut ancestor_path = inclusive_ancestor_paths
668 .next()
680 .next()
669 .expect("expected at least one inclusive ancestor");
681 .expect("expected at least one inclusive ancestor");
670 loop {
682 loop {
671 let (_, child_node) = child_nodes
683 let (_, child_node) = child_nodes
672 .make_mut(on_disk, unreachable_bytes)?
684 .make_mut(on_disk, unreachable_bytes)?
673 .raw_entry_mut()
685 .raw_entry_mut()
674 .from_key(ancestor_path.base_name())
686 .from_key(ancestor_path.base_name())
675 .or_insert_with(|| (to_cow(ancestor_path), Node::default()));
687 .or_insert_with(|| (to_cow(ancestor_path), Node::default()));
676 if let Some(next) = inclusive_ancestor_paths.next() {
688 if let Some(next) = inclusive_ancestor_paths.next() {
677 each_ancestor(child_node);
689 each_ancestor(child_node);
678 ancestor_path = next;
690 ancestor_path = next;
679 child_nodes = &mut child_node.children;
691 child_nodes = &mut child_node.children;
680 } else {
692 } else {
681 return Ok(child_node);
693 return Ok(child_node);
682 }
694 }
683 }
695 }
684 }
696 }
685
697
686 fn reset_state(
698 fn reset_state(
687 &mut self,
699 &mut self,
688 filename: &HgPath,
700 filename: &HgPath,
689 old_entry_opt: Option<DirstateEntry>,
701 old_entry_opt: Option<DirstateEntry>,
690 wc_tracked: bool,
702 wc_tracked: bool,
691 p1_tracked: bool,
703 p1_tracked: bool,
692 p2_info: bool,
704 p2_info: bool,
693 has_meaningful_mtime: bool,
705 has_meaningful_mtime: bool,
694 parent_file_data_opt: Option<ParentFileData>,
706 parent_file_data_opt: Option<ParentFileData>,
695 ) -> Result<(), DirstateError> {
707 ) -> Result<(), DirstateError> {
696 let (had_entry, was_tracked) = match old_entry_opt {
708 let (had_entry, was_tracked) = match old_entry_opt {
697 Some(old_entry) => (true, old_entry.tracked()),
709 Some(old_entry) => (true, old_entry.tracked()),
698 None => (false, false),
710 None => (false, false),
699 };
711 };
700 let node = self.get_or_insert_node(filename, |ancestor| {
712 let node = self.get_or_insert_node(filename, |ancestor| {
701 if !had_entry {
713 if !had_entry {
702 ancestor.descendants_with_entry_count += 1;
714 ancestor.descendants_with_entry_count += 1;
703 }
715 }
704 if was_tracked {
716 if was_tracked {
705 if !wc_tracked {
717 if !wc_tracked {
706 ancestor.tracked_descendants_count = ancestor
718 ancestor.tracked_descendants_count = ancestor
707 .tracked_descendants_count
719 .tracked_descendants_count
708 .checked_sub(1)
720 .checked_sub(1)
709 .expect("tracked count to be >= 0");
721 .expect("tracked count to be >= 0");
710 }
722 }
711 } else {
723 } else {
712 if wc_tracked {
724 if wc_tracked {
713 ancestor.tracked_descendants_count += 1;
725 ancestor.tracked_descendants_count += 1;
714 }
726 }
715 }
727 }
716 })?;
728 })?;
717
729
718 let v2_data = if let Some(parent_file_data) = parent_file_data_opt {
730 let v2_data = if let Some(parent_file_data) = parent_file_data_opt {
719 DirstateV2Data {
731 DirstateV2Data {
720 wc_tracked,
732 wc_tracked,
721 p1_tracked,
733 p1_tracked,
722 p2_info,
734 p2_info,
723 mode_size: parent_file_data.mode_size,
735 mode_size: parent_file_data.mode_size,
724 mtime: if has_meaningful_mtime {
736 mtime: if has_meaningful_mtime {
725 parent_file_data.mtime
737 parent_file_data.mtime
726 } else {
738 } else {
727 None
739 None
728 },
740 },
729 ..Default::default()
741 ..Default::default()
730 }
742 }
731 } else {
743 } else {
732 DirstateV2Data {
744 DirstateV2Data {
733 wc_tracked,
745 wc_tracked,
734 p1_tracked,
746 p1_tracked,
735 p2_info,
747 p2_info,
736 ..Default::default()
748 ..Default::default()
737 }
749 }
738 };
750 };
739 node.data = NodeData::Entry(DirstateEntry::from_v2_data(v2_data));
751 node.data = NodeData::Entry(DirstateEntry::from_v2_data(v2_data));
740 if !had_entry {
752 if !had_entry {
741 self.nodes_with_entry_count += 1;
753 self.nodes_with_entry_count += 1;
742 }
754 }
743 Ok(())
755 Ok(())
744 }
756 }
745
757
746 fn set_tracked(
758 fn set_tracked(
747 &mut self,
759 &mut self,
748 filename: &HgPath,
760 filename: &HgPath,
749 old_entry_opt: Option<DirstateEntry>,
761 old_entry_opt: Option<DirstateEntry>,
750 ) -> Result<bool, DirstateV2ParseError> {
762 ) -> Result<bool, DirstateV2ParseError> {
751 let was_tracked = old_entry_opt.map_or(false, |e| e.tracked());
763 let was_tracked = old_entry_opt.map_or(false, |e| e.tracked());
752 let had_entry = old_entry_opt.is_some();
764 let had_entry = old_entry_opt.is_some();
753 let tracked_count_increment = if was_tracked { 0 } else { 1 };
765 let tracked_count_increment = if was_tracked { 0 } else { 1 };
754 let mut new = false;
766 let mut new = false;
755
767
756 let node = self.get_or_insert_node(filename, |ancestor| {
768 let node = self.get_or_insert_node(filename, |ancestor| {
757 if !had_entry {
769 if !had_entry {
758 ancestor.descendants_with_entry_count += 1;
770 ancestor.descendants_with_entry_count += 1;
759 }
771 }
760
772
761 ancestor.tracked_descendants_count += tracked_count_increment;
773 ancestor.tracked_descendants_count += tracked_count_increment;
762 })?;
774 })?;
763 if let Some(old_entry) = old_entry_opt {
775 if let Some(old_entry) = old_entry_opt {
764 let mut e = old_entry.clone();
776 let mut e = old_entry.clone();
765 if e.tracked() {
777 if e.tracked() {
766 // XXX
778 // XXX
767 // This is probably overkill for more case, but we need this to
779 // This is probably overkill for more case, but we need this to
768 // fully replace the `normallookup` call with `set_tracked`
780 // fully replace the `normallookup` call with `set_tracked`
769 // one. Consider smoothing this in the future.
781 // one. Consider smoothing this in the future.
770 e.set_possibly_dirty();
782 e.set_possibly_dirty();
771 } else {
783 } else {
772 new = true;
784 new = true;
773 e.set_tracked();
785 e.set_tracked();
774 }
786 }
775 node.data = NodeData::Entry(e)
787 node.data = NodeData::Entry(e)
776 } else {
788 } else {
777 node.data = NodeData::Entry(DirstateEntry::new_tracked());
789 node.data = NodeData::Entry(DirstateEntry::new_tracked());
778 self.nodes_with_entry_count += 1;
790 self.nodes_with_entry_count += 1;
779 new = true;
791 new = true;
780 };
792 };
781 Ok(new)
793 Ok(new)
782 }
794 }
783
795
784 /// Set a node as untracked in the dirstate.
796 /// Set a node as untracked in the dirstate.
785 ///
797 ///
786 /// It is the responsibility of the caller to remove the copy source and/or
798 /// It is the responsibility of the caller to remove the copy source and/or
787 /// the entry itself if appropriate.
799 /// the entry itself if appropriate.
788 ///
800 ///
789 /// # Panics
801 /// # Panics
790 ///
802 ///
791 /// Panics if the node does not exist.
803 /// Panics if the node does not exist.
792 fn set_untracked(
804 fn set_untracked(
793 &mut self,
805 &mut self,
794 filename: &HgPath,
806 filename: &HgPath,
795 old_entry: DirstateEntry,
807 old_entry: DirstateEntry,
796 ) -> Result<(), DirstateV2ParseError> {
808 ) -> Result<(), DirstateV2ParseError> {
797 let node = self
809 let node = self
798 .get_node_mut(filename, |ancestor| {
810 .get_node_mut(filename, |ancestor| {
799 ancestor.tracked_descendants_count = ancestor
811 ancestor.tracked_descendants_count = ancestor
800 .tracked_descendants_count
812 .tracked_descendants_count
801 .checked_sub(1)
813 .checked_sub(1)
802 .expect("tracked_descendants_count should be >= 0");
814 .expect("tracked_descendants_count should be >= 0");
803 })?
815 })?
804 .expect("node should exist");
816 .expect("node should exist");
805 let mut new_entry = old_entry.clone();
817 let mut new_entry = old_entry.clone();
806 new_entry.set_untracked();
818 new_entry.set_untracked();
807 node.data = NodeData::Entry(new_entry);
819 node.data = NodeData::Entry(new_entry);
808 Ok(())
820 Ok(())
809 }
821 }
810
822
811 /// Set a node as clean in the dirstate.
823 /// Set a node as clean in the dirstate.
812 ///
824 ///
813 /// It is the responsibility of the caller to remove the copy source.
825 /// It is the responsibility of the caller to remove the copy source.
814 ///
826 ///
815 /// # Panics
827 /// # Panics
816 ///
828 ///
817 /// Panics if the node does not exist.
829 /// Panics if the node does not exist.
818 fn set_clean(
830 fn set_clean(
819 &mut self,
831 &mut self,
820 filename: &HgPath,
832 filename: &HgPath,
821 old_entry: DirstateEntry,
833 old_entry: DirstateEntry,
822 mode: u32,
834 mode: u32,
823 size: u32,
835 size: u32,
824 mtime: TruncatedTimestamp,
836 mtime: TruncatedTimestamp,
825 ) -> Result<(), DirstateError> {
837 ) -> Result<(), DirstateError> {
826 let node = self
838 let node = self
827 .get_node_mut(filename, |ancestor| {
839 .get_node_mut(filename, |ancestor| {
828 if !old_entry.tracked() {
840 if !old_entry.tracked() {
829 ancestor.tracked_descendants_count += 1;
841 ancestor.tracked_descendants_count += 1;
830 }
842 }
831 })?
843 })?
832 .expect("node should exist");
844 .expect("node should exist");
833 let mut new_entry = old_entry.clone();
845 let mut new_entry = old_entry.clone();
834 new_entry.set_clean(mode, size, mtime);
846 new_entry.set_clean(mode, size, mtime);
835 node.data = NodeData::Entry(new_entry);
847 node.data = NodeData::Entry(new_entry);
836 Ok(())
848 Ok(())
837 }
849 }
838
850
839 /// Set a node as possibly dirty in the dirstate.
851 /// Set a node as possibly dirty in the dirstate.
840 ///
852 ///
841 /// # Panics
853 /// # Panics
842 ///
854 ///
843 /// Panics if the node does not exist.
855 /// Panics if the node does not exist.
844 fn set_possibly_dirty(
856 fn set_possibly_dirty(
845 &mut self,
857 &mut self,
846 filename: &HgPath,
858 filename: &HgPath,
847 ) -> Result<(), DirstateError> {
859 ) -> Result<(), DirstateError> {
848 let node = self
860 let node = self
849 .get_node_mut(filename, |_ancestor| {})?
861 .get_node_mut(filename, |_ancestor| {})?
850 .expect("node should exist");
862 .expect("node should exist");
851 let entry = node.data.as_entry_mut().expect("entry should exist");
863 let entry = node.data.as_entry_mut().expect("entry should exist");
852 entry.set_possibly_dirty();
864 entry.set_possibly_dirty();
853 node.data = NodeData::Entry(*entry);
865 node.data = NodeData::Entry(*entry);
854 Ok(())
866 Ok(())
855 }
867 }
856
868
857 /// Clears the cached mtime for the (potential) folder at `path`.
869 /// Clears the cached mtime for the (potential) folder at `path`.
858 pub(super) fn clear_cached_mtime(
870 pub(super) fn clear_cached_mtime(
859 &mut self,
871 &mut self,
860 path: &HgPath,
872 path: &HgPath,
861 ) -> Result<(), DirstateV2ParseError> {
873 ) -> Result<(), DirstateV2ParseError> {
862 let node = match self.get_node_mut(path, |_ancestor| {})? {
874 let node = match self.get_node_mut(path, |_ancestor| {})? {
863 Some(node) => node,
875 Some(node) => node,
864 None => return Ok(()),
876 None => return Ok(()),
865 };
877 };
866 if let NodeData::CachedDirectory { .. } = &node.data {
878 if let NodeData::CachedDirectory { .. } = &node.data {
867 node.data = NodeData::None
879 node.data = NodeData::None
868 }
880 }
869 Ok(())
881 Ok(())
870 }
882 }
871
883
872 /// Sets the cached mtime for the (potential) folder at `path`.
884 /// Sets the cached mtime for the (potential) folder at `path`.
873 pub(super) fn set_cached_mtime(
885 pub(super) fn set_cached_mtime(
874 &mut self,
886 &mut self,
875 path: &HgPath,
887 path: &HgPath,
876 mtime: TruncatedTimestamp,
888 mtime: TruncatedTimestamp,
877 ) -> Result<(), DirstateV2ParseError> {
889 ) -> Result<(), DirstateV2ParseError> {
878 let node = match self.get_node_mut(path, |_ancestor| {})? {
890 let node = match self.get_node_mut(path, |_ancestor| {})? {
879 Some(node) => node,
891 Some(node) => node,
880 None => return Ok(()),
892 None => return Ok(()),
881 };
893 };
882 match &node.data {
894 match &node.data {
883 NodeData::Entry(_) => {} // Don’t overwrite an entry
895 NodeData::Entry(_) => {} // Don’t overwrite an entry
884 NodeData::CachedDirectory { .. } | NodeData::None => {
896 NodeData::CachedDirectory { .. } | NodeData::None => {
885 node.data = NodeData::CachedDirectory { mtime }
897 node.data = NodeData::CachedDirectory { mtime }
886 }
898 }
887 }
899 }
888 Ok(())
900 Ok(())
889 }
901 }
890
902
891 fn iter_nodes<'tree>(
903 fn iter_nodes<'tree>(
892 &'tree self,
904 &'tree self,
893 ) -> impl Iterator<
905 ) -> impl Iterator<
894 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
906 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
895 > + 'tree {
907 > + 'tree {
896 // Depth first tree traversal.
908 // Depth first tree traversal.
897 //
909 //
898 // If we could afford internal iteration and recursion,
910 // If we could afford internal iteration and recursion,
899 // this would look like:
911 // this would look like:
900 //
912 //
901 // ```
913 // ```
902 // fn traverse_children(
914 // fn traverse_children(
903 // children: &ChildNodes,
915 // children: &ChildNodes,
904 // each: &mut impl FnMut(&Node),
916 // each: &mut impl FnMut(&Node),
905 // ) {
917 // ) {
906 // for child in children.values() {
918 // for child in children.values() {
907 // traverse_children(&child.children, each);
919 // traverse_children(&child.children, each);
908 // each(child);
920 // each(child);
909 // }
921 // }
910 // }
922 // }
911 // ```
923 // ```
912 //
924 //
913 // However we want an external iterator and therefore can’t use the
925 // However we want an external iterator and therefore can’t use the
914 // call stack. Use an explicit stack instead:
926 // call stack. Use an explicit stack instead:
915 let mut stack = Vec::new();
927 let mut stack = Vec::new();
916 let mut iter = self.root.as_ref().iter();
928 let mut iter = self.root.as_ref().iter();
917 std::iter::from_fn(move || {
929 std::iter::from_fn(move || {
918 while let Some(child_node) = iter.next() {
930 while let Some(child_node) = iter.next() {
919 let children = match child_node.children(self.on_disk) {
931 let children = match child_node.children(self.on_disk) {
920 Ok(children) => children,
932 Ok(children) => children,
921 Err(error) => return Some(Err(error)),
933 Err(error) => return Some(Err(error)),
922 };
934 };
923 // Pseudo-recursion
935 // Pseudo-recursion
924 let new_iter = children.iter();
936 let new_iter = children.iter();
925 let old_iter = std::mem::replace(&mut iter, new_iter);
937 let old_iter = std::mem::replace(&mut iter, new_iter);
926 stack.push((child_node, old_iter));
938 stack.push((child_node, old_iter));
927 }
939 }
928 // Found the end of a `children.iter()` iterator.
940 // Found the end of a `children.iter()` iterator.
929 if let Some((child_node, next_iter)) = stack.pop() {
941 if let Some((child_node, next_iter)) = stack.pop() {
930 // "Return" from pseudo-recursion by restoring state from the
942 // "Return" from pseudo-recursion by restoring state from the
931 // explicit stack
943 // explicit stack
932 iter = next_iter;
944 iter = next_iter;
933
945
934 Some(Ok(child_node))
946 Some(Ok(child_node))
935 } else {
947 } else {
936 // Reached the bottom of the stack, we’re done
948 // Reached the bottom of the stack, we’re done
937 None
949 None
938 }
950 }
939 })
951 })
940 }
952 }
941
953
942 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
954 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
943 if let Cow::Borrowed(path) = path {
955 if let Cow::Borrowed(path) = path {
944 *unreachable_bytes += path.len() as u32
956 *unreachable_bytes += path.len() as u32
945 }
957 }
946 }
958 }
947
959
948 pub(crate) fn set_write_mode(&mut self, write_mode: DirstateMapWriteMode) {
960 pub(crate) fn set_write_mode(&mut self, write_mode: DirstateMapWriteMode) {
949 self.write_mode = write_mode;
961 self.write_mode = write_mode;
950 }
962 }
951 }
963 }
952
964
953 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
965 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
954 ///
966 ///
955 /// The callback is only called for incoming `Ok` values. Errors are passed
967 /// The callback is only called for incoming `Ok` values. Errors are passed
956 /// through as-is. In order to let it use the `?` operator the callback is
968 /// through as-is. In order to let it use the `?` operator the callback is
957 /// expected to return a `Result` of `Option`, instead of an `Option` of
969 /// expected to return a `Result` of `Option`, instead of an `Option` of
958 /// `Result`.
970 /// `Result`.
959 fn filter_map_results<'a, I, F, A, B, E>(
971 fn filter_map_results<'a, I, F, A, B, E>(
960 iter: I,
972 iter: I,
961 f: F,
973 f: F,
962 ) -> impl Iterator<Item = Result<B, E>> + 'a
974 ) -> impl Iterator<Item = Result<B, E>> + 'a
963 where
975 where
964 I: Iterator<Item = Result<A, E>> + 'a,
976 I: Iterator<Item = Result<A, E>> + 'a,
965 F: Fn(A) -> Result<Option<B>, E> + 'a,
977 F: Fn(A) -> Result<Option<B>, E> + 'a,
966 {
978 {
967 iter.filter_map(move |result| match result {
979 iter.filter_map(move |result| match result {
968 Ok(node) => f(node).transpose(),
980 Ok(node) => f(node).transpose(),
969 Err(e) => Some(Err(e)),
981 Err(e) => Some(Err(e)),
970 })
982 })
971 }
983 }
972
984
973 impl OwningDirstateMap {
985 impl OwningDirstateMap {
974 pub fn clear(&mut self) {
986 pub fn clear(&mut self) {
975 self.with_dmap_mut(|map| {
987 self.with_dmap_mut(|map| {
976 map.root = Default::default();
988 map.root = Default::default();
977 map.nodes_with_entry_count = 0;
989 map.nodes_with_entry_count = 0;
978 map.nodes_with_copy_source_count = 0;
990 map.nodes_with_copy_source_count = 0;
979 });
991 });
980 }
992 }
981
993
982 pub fn set_tracked(
994 pub fn set_tracked(
983 &mut self,
995 &mut self,
984 filename: &HgPath,
996 filename: &HgPath,
985 ) -> Result<bool, DirstateV2ParseError> {
997 ) -> Result<bool, DirstateV2ParseError> {
986 let old_entry_opt = self.get(filename)?;
998 let old_entry_opt = self.get(filename)?;
987 self.with_dmap_mut(|map| map.set_tracked(filename, old_entry_opt))
999 self.with_dmap_mut(|map| map.set_tracked(filename, old_entry_opt))
988 }
1000 }
989
1001
990 pub fn set_untracked(
1002 pub fn set_untracked(
991 &mut self,
1003 &mut self,
992 filename: &HgPath,
1004 filename: &HgPath,
993 ) -> Result<bool, DirstateError> {
1005 ) -> Result<bool, DirstateError> {
994 let old_entry_opt = self.get(filename)?;
1006 let old_entry_opt = self.get(filename)?;
995 match old_entry_opt {
1007 match old_entry_opt {
996 None => Ok(false),
1008 None => Ok(false),
997 Some(old_entry) => {
1009 Some(old_entry) => {
998 if !old_entry.tracked() {
1010 if !old_entry.tracked() {
999 // `DirstateMap::set_untracked` is not a noop if
1011 // `DirstateMap::set_untracked` is not a noop if
1000 // already not tracked as it will decrement the
1012 // already not tracked as it will decrement the
1001 // tracked counters while going down.
1013 // tracked counters while going down.
1002 return Ok(true);
1014 return Ok(true);
1003 }
1015 }
1004 if old_entry.added() {
1016 if old_entry.added() {
1005 // Untracking an "added" entry will just result in a
1017 // Untracking an "added" entry will just result in a
1006 // worthless entry (and other parts of the code will
1018 // worthless entry (and other parts of the code will
1007 // complain about it), just drop it entirely.
1019 // complain about it), just drop it entirely.
1008 self.drop_entry_and_copy_source(filename)?;
1020 self.drop_entry_and_copy_source(filename)?;
1009 return Ok(true);
1021 return Ok(true);
1010 }
1022 }
1011 if !old_entry.p2_info() {
1023 if !old_entry.p2_info() {
1012 self.copy_map_remove(filename)?;
1024 self.copy_map_remove(filename)?;
1013 }
1025 }
1014
1026
1015 self.with_dmap_mut(|map| {
1027 self.with_dmap_mut(|map| {
1016 map.set_untracked(filename, old_entry)?;
1028 map.set_untracked(filename, old_entry)?;
1017 Ok(true)
1029 Ok(true)
1018 })
1030 })
1019 }
1031 }
1020 }
1032 }
1021 }
1033 }
1022
1034
1023 pub fn set_clean(
1035 pub fn set_clean(
1024 &mut self,
1036 &mut self,
1025 filename: &HgPath,
1037 filename: &HgPath,
1026 mode: u32,
1038 mode: u32,
1027 size: u32,
1039 size: u32,
1028 mtime: TruncatedTimestamp,
1040 mtime: TruncatedTimestamp,
1029 ) -> Result<(), DirstateError> {
1041 ) -> Result<(), DirstateError> {
1030 let old_entry = match self.get(filename)? {
1042 let old_entry = match self.get(filename)? {
1031 None => {
1043 None => {
1032 return Err(
1044 return Err(
1033 DirstateMapError::PathNotFound(filename.into()).into()
1045 DirstateMapError::PathNotFound(filename.into()).into()
1034 )
1046 )
1035 }
1047 }
1036 Some(e) => e,
1048 Some(e) => e,
1037 };
1049 };
1038 self.copy_map_remove(filename)?;
1050 self.copy_map_remove(filename)?;
1039 self.with_dmap_mut(|map| {
1051 self.with_dmap_mut(|map| {
1040 map.set_clean(filename, old_entry, mode, size, mtime)
1052 map.set_clean(filename, old_entry, mode, size, mtime)
1041 })
1053 })
1042 }
1054 }
1043
1055
1044 pub fn set_possibly_dirty(
1056 pub fn set_possibly_dirty(
1045 &mut self,
1057 &mut self,
1046 filename: &HgPath,
1058 filename: &HgPath,
1047 ) -> Result<(), DirstateError> {
1059 ) -> Result<(), DirstateError> {
1048 if self.get(filename)?.is_none() {
1060 if self.get(filename)?.is_none() {
1049 return Err(DirstateMapError::PathNotFound(filename.into()).into());
1061 return Err(DirstateMapError::PathNotFound(filename.into()).into());
1050 }
1062 }
1051 self.with_dmap_mut(|map| map.set_possibly_dirty(filename))
1063 self.with_dmap_mut(|map| map.set_possibly_dirty(filename))
1052 }
1064 }
1053
1065
1054 pub fn reset_state(
1066 pub fn reset_state(
1055 &mut self,
1067 &mut self,
1056 filename: &HgPath,
1068 filename: &HgPath,
1057 wc_tracked: bool,
1069 wc_tracked: bool,
1058 p1_tracked: bool,
1070 p1_tracked: bool,
1059 p2_info: bool,
1071 p2_info: bool,
1060 has_meaningful_mtime: bool,
1072 has_meaningful_mtime: bool,
1061 parent_file_data_opt: Option<ParentFileData>,
1073 parent_file_data_opt: Option<ParentFileData>,
1062 ) -> Result<(), DirstateError> {
1074 ) -> Result<(), DirstateError> {
1063 if !(p1_tracked || p2_info || wc_tracked) {
1075 if !(p1_tracked || p2_info || wc_tracked) {
1064 self.drop_entry_and_copy_source(filename)?;
1076 self.drop_entry_and_copy_source(filename)?;
1065 return Ok(());
1077 return Ok(());
1066 }
1078 }
1067 self.copy_map_remove(filename)?;
1079 self.copy_map_remove(filename)?;
1068 let old_entry_opt = self.get(filename)?;
1080 let old_entry_opt = self.get(filename)?;
1069 self.with_dmap_mut(|map| {
1081 self.with_dmap_mut(|map| {
1070 map.reset_state(
1082 map.reset_state(
1071 filename,
1083 filename,
1072 old_entry_opt,
1084 old_entry_opt,
1073 wc_tracked,
1085 wc_tracked,
1074 p1_tracked,
1086 p1_tracked,
1075 p2_info,
1087 p2_info,
1076 has_meaningful_mtime,
1088 has_meaningful_mtime,
1077 parent_file_data_opt,
1089 parent_file_data_opt,
1078 )
1090 )
1079 })
1091 })
1080 }
1092 }
1081
1093
1082 pub fn drop_entry_and_copy_source(
1094 pub fn drop_entry_and_copy_source(
1083 &mut self,
1095 &mut self,
1084 filename: &HgPath,
1096 filename: &HgPath,
1085 ) -> Result<(), DirstateError> {
1097 ) -> Result<(), DirstateError> {
1086 let was_tracked = self.get(filename)?.map_or(false, |e| e.tracked());
1098 let was_tracked = self.get(filename)?.map_or(false, |e| e.tracked());
1087 struct Dropped {
1099 struct Dropped {
1088 was_tracked: bool,
1100 was_tracked: bool,
1089 had_entry: bool,
1101 had_entry: bool,
1090 had_copy_source: bool,
1102 had_copy_source: bool,
1091 }
1103 }
1092
1104
1093 /// If this returns `Ok(Some((dropped, removed)))`, then
1105 /// If this returns `Ok(Some((dropped, removed)))`, then
1094 ///
1106 ///
1095 /// * `dropped` is about the leaf node that was at `filename`
1107 /// * `dropped` is about the leaf node that was at `filename`
1096 /// * `removed` is whether this particular level of recursion just
1108 /// * `removed` is whether this particular level of recursion just
1097 /// removed a node in `nodes`.
1109 /// removed a node in `nodes`.
1098 fn recur<'on_disk>(
1110 fn recur<'on_disk>(
1099 on_disk: &'on_disk [u8],
1111 on_disk: &'on_disk [u8],
1100 unreachable_bytes: &mut u32,
1112 unreachable_bytes: &mut u32,
1101 nodes: &mut ChildNodes<'on_disk>,
1113 nodes: &mut ChildNodes<'on_disk>,
1102 path: &HgPath,
1114 path: &HgPath,
1103 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
1115 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
1104 let (first_path_component, rest_of_path) =
1116 let (first_path_component, rest_of_path) =
1105 path.split_first_component();
1117 path.split_first_component();
1106 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
1118 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
1107 let node = if let Some(node) = nodes.get_mut(first_path_component)
1119 let node = if let Some(node) = nodes.get_mut(first_path_component)
1108 {
1120 {
1109 node
1121 node
1110 } else {
1122 } else {
1111 return Ok(None);
1123 return Ok(None);
1112 };
1124 };
1113 let dropped;
1125 let dropped;
1114 if let Some(rest) = rest_of_path {
1126 if let Some(rest) = rest_of_path {
1115 if let Some((d, removed)) = recur(
1127 if let Some((d, removed)) = recur(
1116 on_disk,
1128 on_disk,
1117 unreachable_bytes,
1129 unreachable_bytes,
1118 &mut node.children,
1130 &mut node.children,
1119 rest,
1131 rest,
1120 )? {
1132 )? {
1121 dropped = d;
1133 dropped = d;
1122 if dropped.had_entry {
1134 if dropped.had_entry {
1123 node.descendants_with_entry_count = node
1135 node.descendants_with_entry_count = node
1124 .descendants_with_entry_count
1136 .descendants_with_entry_count
1125 .checked_sub(1)
1137 .checked_sub(1)
1126 .expect(
1138 .expect(
1127 "descendants_with_entry_count should be >= 0",
1139 "descendants_with_entry_count should be >= 0",
1128 );
1140 );
1129 }
1141 }
1130 if dropped.was_tracked {
1142 if dropped.was_tracked {
1131 node.tracked_descendants_count = node
1143 node.tracked_descendants_count = node
1132 .tracked_descendants_count
1144 .tracked_descendants_count
1133 .checked_sub(1)
1145 .checked_sub(1)
1134 .expect(
1146 .expect(
1135 "tracked_descendants_count should be >= 0",
1147 "tracked_descendants_count should be >= 0",
1136 );
1148 );
1137 }
1149 }
1138
1150
1139 // Directory caches must be invalidated when removing a
1151 // Directory caches must be invalidated when removing a
1140 // child node
1152 // child node
1141 if removed {
1153 if removed {
1142 if let NodeData::CachedDirectory { .. } = &node.data {
1154 if let NodeData::CachedDirectory { .. } = &node.data {
1143 node.data = NodeData::None
1155 node.data = NodeData::None
1144 }
1156 }
1145 }
1157 }
1146 } else {
1158 } else {
1147 return Ok(None);
1159 return Ok(None);
1148 }
1160 }
1149 } else {
1161 } else {
1150 let entry = node.data.as_entry();
1162 let entry = node.data.as_entry();
1151 let was_tracked = entry.map_or(false, |entry| entry.tracked());
1163 let was_tracked = entry.map_or(false, |entry| entry.tracked());
1152 let had_entry = entry.is_some();
1164 let had_entry = entry.is_some();
1153 if had_entry {
1165 if had_entry {
1154 node.data = NodeData::None
1166 node.data = NodeData::None
1155 }
1167 }
1156 let mut had_copy_source = false;
1168 let mut had_copy_source = false;
1157 if let Some(source) = &node.copy_source {
1169 if let Some(source) = &node.copy_source {
1158 DirstateMap::count_dropped_path(unreachable_bytes, source);
1170 DirstateMap::count_dropped_path(unreachable_bytes, source);
1159 had_copy_source = true;
1171 had_copy_source = true;
1160 node.copy_source = None
1172 node.copy_source = None
1161 }
1173 }
1162 dropped = Dropped {
1174 dropped = Dropped {
1163 was_tracked,
1175 was_tracked,
1164 had_entry,
1176 had_entry,
1165 had_copy_source,
1177 had_copy_source,
1166 };
1178 };
1167 }
1179 }
1168 // After recursion, for both leaf (rest_of_path is None) nodes and
1180 // After recursion, for both leaf (rest_of_path is None) nodes and
1169 // parent nodes, remove a node if it just became empty.
1181 // parent nodes, remove a node if it just became empty.
1170 let remove = !node.data.has_entry()
1182 let remove = !node.data.has_entry()
1171 && node.copy_source.is_none()
1183 && node.copy_source.is_none()
1172 && node.children.is_empty();
1184 && node.children.is_empty();
1173 if remove {
1185 if remove {
1174 let (key, _) =
1186 let (key, _) =
1175 nodes.remove_entry(first_path_component).unwrap();
1187 nodes.remove_entry(first_path_component).unwrap();
1176 DirstateMap::count_dropped_path(
1188 DirstateMap::count_dropped_path(
1177 unreachable_bytes,
1189 unreachable_bytes,
1178 key.full_path(),
1190 key.full_path(),
1179 )
1191 )
1180 }
1192 }
1181 Ok(Some((dropped, remove)))
1193 Ok(Some((dropped, remove)))
1182 }
1194 }
1183
1195
1184 self.with_dmap_mut(|map| {
1196 self.with_dmap_mut(|map| {
1185 if let Some((dropped, _removed)) = recur(
1197 if let Some((dropped, _removed)) = recur(
1186 map.on_disk,
1198 map.on_disk,
1187 &mut map.unreachable_bytes,
1199 &mut map.unreachable_bytes,
1188 &mut map.root,
1200 &mut map.root,
1189 filename,
1201 filename,
1190 )? {
1202 )? {
1191 if dropped.had_entry {
1203 if dropped.had_entry {
1192 map.nodes_with_entry_count = map
1204 map.nodes_with_entry_count = map
1193 .nodes_with_entry_count
1205 .nodes_with_entry_count
1194 .checked_sub(1)
1206 .checked_sub(1)
1195 .expect("nodes_with_entry_count should be >= 0");
1207 .expect("nodes_with_entry_count should be >= 0");
1196 }
1208 }
1197 if dropped.had_copy_source {
1209 if dropped.had_copy_source {
1198 map.nodes_with_copy_source_count = map
1210 map.nodes_with_copy_source_count = map
1199 .nodes_with_copy_source_count
1211 .nodes_with_copy_source_count
1200 .checked_sub(1)
1212 .checked_sub(1)
1201 .expect("nodes_with_copy_source_count should be >= 0");
1213 .expect("nodes_with_copy_source_count should be >= 0");
1202 }
1214 }
1203 } else {
1215 } else {
1204 debug_assert!(!was_tracked);
1216 debug_assert!(!was_tracked);
1205 }
1217 }
1206 Ok(())
1218 Ok(())
1207 })
1219 })
1208 }
1220 }
1209
1221
1210 pub fn has_tracked_dir(
1222 pub fn has_tracked_dir(
1211 &mut self,
1223 &mut self,
1212 directory: &HgPath,
1224 directory: &HgPath,
1213 ) -> Result<bool, DirstateError> {
1225 ) -> Result<bool, DirstateError> {
1214 self.with_dmap_mut(|map| {
1226 self.with_dmap_mut(|map| {
1215 if let Some(node) = map.get_node(directory)? {
1227 if let Some(node) = map.get_node(directory)? {
1216 // A node without a `DirstateEntry` was created to hold child
1228 // A node without a `DirstateEntry` was created to hold child
1217 // nodes, and is therefore a directory.
1229 // nodes, and is therefore a directory.
1218 let is_dir = node.entry()?.is_none();
1230 let is_dir = node.entry()?.is_none();
1219 Ok(is_dir && node.tracked_descendants_count() > 0)
1231 Ok(is_dir && node.tracked_descendants_count() > 0)
1220 } else {
1232 } else {
1221 Ok(false)
1233 Ok(false)
1222 }
1234 }
1223 })
1235 })
1224 }
1236 }
1225
1237
1226 pub fn has_dir(
1238 pub fn has_dir(
1227 &mut self,
1239 &mut self,
1228 directory: &HgPath,
1240 directory: &HgPath,
1229 ) -> Result<bool, DirstateError> {
1241 ) -> Result<bool, DirstateError> {
1230 self.with_dmap_mut(|map| {
1242 self.with_dmap_mut(|map| {
1231 if let Some(node) = map.get_node(directory)? {
1243 if let Some(node) = map.get_node(directory)? {
1232 // A node without a `DirstateEntry` was created to hold child
1244 // A node without a `DirstateEntry` was created to hold child
1233 // nodes, and is therefore a directory.
1245 // nodes, and is therefore a directory.
1234 let is_dir = node.entry()?.is_none();
1246 let is_dir = node.entry()?.is_none();
1235 Ok(is_dir && node.descendants_with_entry_count() > 0)
1247 Ok(is_dir && node.descendants_with_entry_count() > 0)
1236 } else {
1248 } else {
1237 Ok(false)
1249 Ok(false)
1238 }
1250 }
1239 })
1251 })
1240 }
1252 }
1241
1253
1242 #[timed]
1254 #[timed]
1243 pub fn pack_v1(
1255 pub fn pack_v1(
1244 &self,
1256 &self,
1245 parents: DirstateParents,
1257 parents: DirstateParents,
1246 ) -> Result<Vec<u8>, DirstateError> {
1258 ) -> Result<Vec<u8>, DirstateError> {
1247 let map = self.get_map();
1259 let map = self.get_map();
1248 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1260 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1249 // reallocations
1261 // reallocations
1250 let mut size = parents.as_bytes().len();
1262 let mut size = parents.as_bytes().len();
1251 for node in map.iter_nodes() {
1263 for node in map.iter_nodes() {
1252 let node = node?;
1264 let node = node?;
1253 if node.entry()?.is_some() {
1265 if node.entry()?.is_some() {
1254 size += packed_entry_size(
1266 size += packed_entry_size(
1255 node.full_path(map.on_disk)?,
1267 node.full_path(map.on_disk)?,
1256 node.copy_source(map.on_disk)?,
1268 node.copy_source(map.on_disk)?,
1257 );
1269 );
1258 }
1270 }
1259 }
1271 }
1260
1272
1261 let mut packed = Vec::with_capacity(size);
1273 let mut packed = Vec::with_capacity(size);
1262 packed.extend(parents.as_bytes());
1274 packed.extend(parents.as_bytes());
1263
1275
1264 for node in map.iter_nodes() {
1276 for node in map.iter_nodes() {
1265 let node = node?;
1277 let node = node?;
1266 if let Some(entry) = node.entry()? {
1278 if let Some(entry) = node.entry()? {
1267 pack_entry(
1279 pack_entry(
1268 node.full_path(map.on_disk)?,
1280 node.full_path(map.on_disk)?,
1269 &entry,
1281 &entry,
1270 node.copy_source(map.on_disk)?,
1282 node.copy_source(map.on_disk)?,
1271 &mut packed,
1283 &mut packed,
1272 );
1284 );
1273 }
1285 }
1274 }
1286 }
1275 Ok(packed)
1287 Ok(packed)
1276 }
1288 }
1277
1289
1278 /// Returns new data and metadata together with whether that data should be
1290 /// Returns new data and metadata together with whether that data should be
1279 /// appended to the existing data file whose content is at
1291 /// appended to the existing data file whose content is at
1280 /// `map.on_disk` (true), instead of written to a new data file
1292 /// `map.on_disk` (true), instead of written to a new data file
1281 /// (false), and the previous size of data on disk.
1293 /// (false), and the previous size of data on disk.
1282 #[timed]
1294 #[timed]
1283 pub fn pack_v2(
1295 pub fn pack_v2(
1284 &self,
1296 &self,
1285 write_mode: DirstateMapWriteMode,
1297 write_mode: DirstateMapWriteMode,
1286 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool, usize), DirstateError>
1298 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool, usize), DirstateError>
1287 {
1299 {
1288 let map = self.get_map();
1300 let map = self.get_map();
1289 on_disk::write(map, write_mode)
1301 on_disk::write(map, write_mode)
1290 }
1302 }
1291
1303
1292 /// `callback` allows the caller to process and do something with the
1304 /// `callback` allows the caller to process and do something with the
1293 /// results of the status. This is needed to do so efficiently (i.e.
1305 /// results of the status. This is needed to do so efficiently (i.e.
1294 /// without cloning the `DirstateStatus` object with its paths) because
1306 /// without cloning the `DirstateStatus` object with its paths) because
1295 /// we need to borrow from `Self`.
1307 /// we need to borrow from `Self`.
1296 pub fn with_status<R>(
1308 pub fn with_status<R>(
1297 &mut self,
1309 &mut self,
1298 matcher: &(dyn Matcher + Sync),
1310 matcher: &(dyn Matcher + Sync),
1299 root_dir: PathBuf,
1311 root_dir: PathBuf,
1300 ignore_files: Vec<PathBuf>,
1312 ignore_files: Vec<PathBuf>,
1301 options: StatusOptions,
1313 options: StatusOptions,
1302 callback: impl for<'r> FnOnce(
1314 callback: impl for<'r> FnOnce(
1303 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
1315 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
1304 ) -> R,
1316 ) -> R,
1305 ) -> R {
1317 ) -> R {
1306 self.with_dmap_mut(|map| {
1318 self.with_dmap_mut(|map| {
1307 callback(super::status::status(
1319 callback(super::status::status(
1308 map,
1320 map,
1309 matcher,
1321 matcher,
1310 root_dir,
1322 root_dir,
1311 ignore_files,
1323 ignore_files,
1312 options,
1324 options,
1313 ))
1325 ))
1314 })
1326 })
1315 }
1327 }
1316
1328
1317 pub fn copy_map_len(&self) -> usize {
1329 pub fn copy_map_len(&self) -> usize {
1318 let map = self.get_map();
1330 let map = self.get_map();
1319 map.nodes_with_copy_source_count as usize
1331 map.nodes_with_copy_source_count as usize
1320 }
1332 }
1321
1333
1322 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1334 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1323 let map = self.get_map();
1335 let map = self.get_map();
1324 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1336 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1325 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1337 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1326 Some((node.full_path(map.on_disk)?, source))
1338 Some((node.full_path(map.on_disk)?, source))
1327 } else {
1339 } else {
1328 None
1340 None
1329 })
1341 })
1330 }))
1342 }))
1331 }
1343 }
1332
1344
1333 pub fn copy_map_contains_key(
1345 pub fn copy_map_contains_key(
1334 &self,
1346 &self,
1335 key: &HgPath,
1347 key: &HgPath,
1336 ) -> Result<bool, DirstateV2ParseError> {
1348 ) -> Result<bool, DirstateV2ParseError> {
1337 let map = self.get_map();
1349 let map = self.get_map();
1338 Ok(if let Some(node) = map.get_node(key)? {
1350 Ok(if let Some(node) = map.get_node(key)? {
1339 node.has_copy_source()
1351 node.has_copy_source()
1340 } else {
1352 } else {
1341 false
1353 false
1342 })
1354 })
1343 }
1355 }
1344
1356
1345 pub fn copy_map_get(
1357 pub fn copy_map_get(
1346 &self,
1358 &self,
1347 key: &HgPath,
1359 key: &HgPath,
1348 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1360 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1349 let map = self.get_map();
1361 let map = self.get_map();
1350 if let Some(node) = map.get_node(key)? {
1362 if let Some(node) = map.get_node(key)? {
1351 if let Some(source) = node.copy_source(map.on_disk)? {
1363 if let Some(source) = node.copy_source(map.on_disk)? {
1352 return Ok(Some(source));
1364 return Ok(Some(source));
1353 }
1365 }
1354 }
1366 }
1355 Ok(None)
1367 Ok(None)
1356 }
1368 }
1357
1369
1358 pub fn copy_map_remove(
1370 pub fn copy_map_remove(
1359 &mut self,
1371 &mut self,
1360 key: &HgPath,
1372 key: &HgPath,
1361 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1373 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1362 self.with_dmap_mut(|map| {
1374 self.with_dmap_mut(|map| {
1363 let count = &mut map.nodes_with_copy_source_count;
1375 let count = &mut map.nodes_with_copy_source_count;
1364 let unreachable_bytes = &mut map.unreachable_bytes;
1376 let unreachable_bytes = &mut map.unreachable_bytes;
1365 Ok(DirstateMap::get_node_mut_inner(
1377 Ok(DirstateMap::get_node_mut_inner(
1366 map.on_disk,
1378 map.on_disk,
1367 unreachable_bytes,
1379 unreachable_bytes,
1368 &mut map.root,
1380 &mut map.root,
1369 key,
1381 key,
1370 |_ancestor| {},
1382 |_ancestor| {},
1371 )?
1383 )?
1372 .and_then(|node| {
1384 .and_then(|node| {
1373 if let Some(source) = &node.copy_source {
1385 if let Some(source) = &node.copy_source {
1374 *count = count
1386 *count = count
1375 .checked_sub(1)
1387 .checked_sub(1)
1376 .expect("nodes_with_copy_source_count should be >= 0");
1388 .expect("nodes_with_copy_source_count should be >= 0");
1377 DirstateMap::count_dropped_path(unreachable_bytes, source);
1389 DirstateMap::count_dropped_path(unreachable_bytes, source);
1378 }
1390 }
1379 node.copy_source.take().map(Cow::into_owned)
1391 node.copy_source.take().map(Cow::into_owned)
1380 }))
1392 }))
1381 })
1393 })
1382 }
1394 }
1383
1395
1384 pub fn copy_map_insert(
1396 pub fn copy_map_insert(
1385 &mut self,
1397 &mut self,
1386 key: &HgPath,
1398 key: &HgPath,
1387 value: &HgPath,
1399 value: &HgPath,
1388 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1400 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1389 self.with_dmap_mut(|map| {
1401 self.with_dmap_mut(|map| {
1390 let node = map.get_or_insert_node(&key, |_ancestor| {})?;
1402 let node = map.get_or_insert_node(&key, |_ancestor| {})?;
1391 let had_copy_source = node.copy_source.is_none();
1403 let had_copy_source = node.copy_source.is_none();
1392 let old = node
1404 let old = node
1393 .copy_source
1405 .copy_source
1394 .replace(value.to_owned().into())
1406 .replace(value.to_owned().into())
1395 .map(Cow::into_owned);
1407 .map(Cow::into_owned);
1396 if had_copy_source {
1408 if had_copy_source {
1397 map.nodes_with_copy_source_count += 1
1409 map.nodes_with_copy_source_count += 1
1398 }
1410 }
1399 Ok(old)
1411 Ok(old)
1400 })
1412 })
1401 }
1413 }
1402
1414
1403 pub fn len(&self) -> usize {
1415 pub fn len(&self) -> usize {
1404 let map = self.get_map();
1416 let map = self.get_map();
1405 map.nodes_with_entry_count as usize
1417 map.nodes_with_entry_count as usize
1406 }
1418 }
1407
1419
1408 pub fn contains_key(
1420 pub fn contains_key(
1409 &self,
1421 &self,
1410 key: &HgPath,
1422 key: &HgPath,
1411 ) -> Result<bool, DirstateV2ParseError> {
1423 ) -> Result<bool, DirstateV2ParseError> {
1412 Ok(self.get(key)?.is_some())
1424 Ok(self.get(key)?.is_some())
1413 }
1425 }
1414
1426
1415 pub fn get(
1427 pub fn get(
1416 &self,
1428 &self,
1417 key: &HgPath,
1429 key: &HgPath,
1418 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1430 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1419 let map = self.get_map();
1431 let map = self.get_map();
1420 Ok(if let Some(node) = map.get_node(key)? {
1432 Ok(if let Some(node) = map.get_node(key)? {
1421 node.entry()?
1433 node.entry()?
1422 } else {
1434 } else {
1423 None
1435 None
1424 })
1436 })
1425 }
1437 }
1426
1438
1427 pub fn iter(&self) -> StateMapIter<'_> {
1439 pub fn iter(&self) -> StateMapIter<'_> {
1428 let map = self.get_map();
1440 let map = self.get_map();
1429 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1441 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1430 Ok(if let Some(entry) = node.entry()? {
1442 Ok(if let Some(entry) = node.entry()? {
1431 Some((node.full_path(map.on_disk)?, entry))
1443 Some((node.full_path(map.on_disk)?, entry))
1432 } else {
1444 } else {
1433 None
1445 None
1434 })
1446 })
1435 }))
1447 }))
1436 }
1448 }
1437
1449
1438 pub fn iter_tracked_dirs(
1450 pub fn iter_tracked_dirs(
1439 &mut self,
1451 &mut self,
1440 ) -> Result<
1452 ) -> Result<
1441 Box<
1453 Box<
1442 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1454 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1443 + Send
1455 + Send
1444 + '_,
1456 + '_,
1445 >,
1457 >,
1446 DirstateError,
1458 DirstateError,
1447 > {
1459 > {
1448 let map = self.get_map();
1460 let map = self.get_map();
1449 let on_disk = map.on_disk;
1461 let on_disk = map.on_disk;
1450 Ok(Box::new(filter_map_results(
1462 Ok(Box::new(filter_map_results(
1451 map.iter_nodes(),
1463 map.iter_nodes(),
1452 move |node| {
1464 move |node| {
1453 Ok(if node.tracked_descendants_count() > 0 {
1465 Ok(if node.tracked_descendants_count() > 0 {
1454 Some(node.full_path(on_disk)?)
1466 Some(node.full_path(on_disk)?)
1455 } else {
1467 } else {
1456 None
1468 None
1457 })
1469 })
1458 },
1470 },
1459 )))
1471 )))
1460 }
1472 }
1461
1473
1462 /// Only public because it needs to be exposed to the Python layer.
1474 /// Only public because it needs to be exposed to the Python layer.
1463 /// It is not the full `setparents` logic, only the parts that mutate the
1475 /// It is not the full `setparents` logic, only the parts that mutate the
1464 /// entries.
1476 /// entries.
1465 pub fn setparents_fixup(
1477 pub fn setparents_fixup(
1466 &mut self,
1478 &mut self,
1467 ) -> Result<Vec<(HgPathBuf, HgPathBuf)>, DirstateV2ParseError> {
1479 ) -> Result<Vec<(HgPathBuf, HgPathBuf)>, DirstateV2ParseError> {
1468 // XXX
1480 // XXX
1469 // All the copying and re-querying is quite inefficient, but this is
1481 // All the copying and re-querying is quite inefficient, but this is
1470 // still a lot better than doing it from Python.
1482 // still a lot better than doing it from Python.
1471 //
1483 //
1472 // The better solution is to develop a mechanism for `iter_mut`,
1484 // The better solution is to develop a mechanism for `iter_mut`,
1473 // which will be a lot more involved: we're dealing with a lazy,
1485 // which will be a lot more involved: we're dealing with a lazy,
1474 // append-mostly, tree-like data structure. This will do for now.
1486 // append-mostly, tree-like data structure. This will do for now.
1475 let mut copies = vec![];
1487 let mut copies = vec![];
1476 let mut files_with_p2_info = vec![];
1488 let mut files_with_p2_info = vec![];
1477 for res in self.iter() {
1489 for res in self.iter() {
1478 let (path, entry) = res?;
1490 let (path, entry) = res?;
1479 if entry.p2_info() {
1491 if entry.p2_info() {
1480 files_with_p2_info.push(path.to_owned())
1492 files_with_p2_info.push(path.to_owned())
1481 }
1493 }
1482 }
1494 }
1483 self.with_dmap_mut(|map| {
1495 self.with_dmap_mut(|map| {
1484 for path in files_with_p2_info.iter() {
1496 for path in files_with_p2_info.iter() {
1485 let node = map.get_or_insert_node(path, |_| {})?;
1497 let node = map.get_or_insert_node(path, |_| {})?;
1486 let entry =
1498 let entry =
1487 node.data.as_entry_mut().expect("entry should exist");
1499 node.data.as_entry_mut().expect("entry should exist");
1488 entry.drop_merge_data();
1500 entry.drop_merge_data();
1489 if let Some(source) = node.copy_source.take().as_deref() {
1501 if let Some(source) = node.copy_source.take().as_deref() {
1490 copies.push((path.to_owned(), source.to_owned()));
1502 copies.push((path.to_owned(), source.to_owned()));
1491 }
1503 }
1492 }
1504 }
1493 Ok(copies)
1505 Ok(copies)
1494 })
1506 })
1495 }
1507 }
1496
1508
1497 pub fn debug_iter(
1509 pub fn debug_iter(
1498 &self,
1510 &self,
1499 all: bool,
1511 all: bool,
1500 ) -> Box<
1512 ) -> Box<
1501 dyn Iterator<
1513 dyn Iterator<
1502 Item = Result<
1514 Item = Result<
1503 (&HgPath, (u8, i32, i32, i32)),
1515 (&HgPath, (u8, i32, i32, i32)),
1504 DirstateV2ParseError,
1516 DirstateV2ParseError,
1505 >,
1517 >,
1506 > + Send
1518 > + Send
1507 + '_,
1519 + '_,
1508 > {
1520 > {
1509 let map = self.get_map();
1521 let map = self.get_map();
1510 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1522 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1511 let debug_tuple = if let Some(entry) = node.entry()? {
1523 let debug_tuple = if let Some(entry) = node.entry()? {
1512 entry.debug_tuple()
1524 entry.debug_tuple()
1513 } else if !all {
1525 } else if !all {
1514 return Ok(None);
1526 return Ok(None);
1515 } else if let Some(mtime) = node.cached_directory_mtime()? {
1527 } else if let Some(mtime) = node.cached_directory_mtime()? {
1516 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1528 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1517 } else {
1529 } else {
1518 (b' ', 0, -1, -1)
1530 (b' ', 0, -1, -1)
1519 };
1531 };
1520 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1532 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1521 }))
1533 }))
1522 }
1534 }
1523 }
1535 }
1524 #[cfg(test)]
1536 #[cfg(test)]
1525 mod tests {
1537 mod tests {
1526 use super::*;
1538 use super::*;
1527
1539
1528 /// Shortcut to return tracked descendants of a path.
1540 /// Shortcut to return tracked descendants of a path.
1529 /// Panics if the path does not exist.
1541 /// Panics if the path does not exist.
1530 fn tracked_descendants(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1542 fn tracked_descendants(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1531 let path = dbg!(HgPath::new(path));
1543 let path = dbg!(HgPath::new(path));
1532 let node = map.get_map().get_node(path);
1544 let node = map.get_map().get_node(path);
1533 node.unwrap().unwrap().tracked_descendants_count()
1545 node.unwrap().unwrap().tracked_descendants_count()
1534 }
1546 }
1535
1547
1536 /// Shortcut to return descendants with an entry.
1548 /// Shortcut to return descendants with an entry.
1537 /// Panics if the path does not exist.
1549 /// Panics if the path does not exist.
1538 fn descendants_with_an_entry(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1550 fn descendants_with_an_entry(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1539 let path = dbg!(HgPath::new(path));
1551 let path = dbg!(HgPath::new(path));
1540 let node = map.get_map().get_node(path);
1552 let node = map.get_map().get_node(path);
1541 node.unwrap().unwrap().descendants_with_entry_count()
1553 node.unwrap().unwrap().descendants_with_entry_count()
1542 }
1554 }
1543
1555
1544 fn assert_does_not_exist(map: &OwningDirstateMap, path: &[u8]) {
1556 fn assert_does_not_exist(map: &OwningDirstateMap, path: &[u8]) {
1545 let path = dbg!(HgPath::new(path));
1557 let path = dbg!(HgPath::new(path));
1546 let node = map.get_map().get_node(path);
1558 let node = map.get_map().get_node(path);
1547 assert!(node.unwrap().is_none());
1559 assert!(node.unwrap().is_none());
1548 }
1560 }
1549
1561
1550 /// Shortcut for path creation in tests
1562 /// Shortcut for path creation in tests
1551 fn p(b: &[u8]) -> &HgPath {
1563 fn p(b: &[u8]) -> &HgPath {
1552 HgPath::new(b)
1564 HgPath::new(b)
1553 }
1565 }
1554
1566
1555 /// Test the very simple case a single tracked file
1567 /// Test the very simple case a single tracked file
1556 #[test]
1568 #[test]
1557 fn test_tracked_descendants_simple() -> Result<(), DirstateError> {
1569 fn test_tracked_descendants_simple() -> Result<(), DirstateError> {
1558 let mut map = OwningDirstateMap::new_empty(vec![]);
1570 let mut map = OwningDirstateMap::new_empty(vec![]);
1559 assert_eq!(map.len(), 0);
1571 assert_eq!(map.len(), 0);
1560
1572
1561 map.set_tracked(p(b"some/nested/path"))?;
1573 map.set_tracked(p(b"some/nested/path"))?;
1562
1574
1563 assert_eq!(map.len(), 1);
1575 assert_eq!(map.len(), 1);
1564 assert_eq!(tracked_descendants(&map, b"some"), 1);
1576 assert_eq!(tracked_descendants(&map, b"some"), 1);
1565 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1577 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1566 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1578 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1567
1579
1568 map.set_untracked(p(b"some/nested/path"))?;
1580 map.set_untracked(p(b"some/nested/path"))?;
1569 assert_eq!(map.len(), 0);
1581 assert_eq!(map.len(), 0);
1570 assert!(map.get_map().get_node(p(b"some"))?.is_none());
1582 assert!(map.get_map().get_node(p(b"some"))?.is_none());
1571
1583
1572 Ok(())
1584 Ok(())
1573 }
1585 }
1574
1586
1575 /// Test the simple case of all tracked, but multiple files
1587 /// Test the simple case of all tracked, but multiple files
1576 #[test]
1588 #[test]
1577 fn test_tracked_descendants_multiple() -> Result<(), DirstateError> {
1589 fn test_tracked_descendants_multiple() -> Result<(), DirstateError> {
1578 let mut map = OwningDirstateMap::new_empty(vec![]);
1590 let mut map = OwningDirstateMap::new_empty(vec![]);
1579
1591
1580 map.set_tracked(p(b"some/nested/path"))?;
1592 map.set_tracked(p(b"some/nested/path"))?;
1581 map.set_tracked(p(b"some/nested/file"))?;
1593 map.set_tracked(p(b"some/nested/file"))?;
1582 // one layer without any files to test deletion cascade
1594 // one layer without any files to test deletion cascade
1583 map.set_tracked(p(b"some/other/nested/path"))?;
1595 map.set_tracked(p(b"some/other/nested/path"))?;
1584 map.set_tracked(p(b"root_file"))?;
1596 map.set_tracked(p(b"root_file"))?;
1585 map.set_tracked(p(b"some/file"))?;
1597 map.set_tracked(p(b"some/file"))?;
1586 map.set_tracked(p(b"some/file2"))?;
1598 map.set_tracked(p(b"some/file2"))?;
1587 map.set_tracked(p(b"some/file3"))?;
1599 map.set_tracked(p(b"some/file3"))?;
1588
1600
1589 assert_eq!(map.len(), 7);
1601 assert_eq!(map.len(), 7);
1590 assert_eq!(tracked_descendants(&map, b"some"), 6);
1602 assert_eq!(tracked_descendants(&map, b"some"), 6);
1591 assert_eq!(tracked_descendants(&map, b"some/nested"), 2);
1603 assert_eq!(tracked_descendants(&map, b"some/nested"), 2);
1592 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1604 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1593 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1605 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1594 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1606 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1595
1607
1596 map.set_untracked(p(b"some/nested/path"))?;
1608 map.set_untracked(p(b"some/nested/path"))?;
1597 assert_eq!(map.len(), 6);
1609 assert_eq!(map.len(), 6);
1598 assert_eq!(tracked_descendants(&map, b"some"), 5);
1610 assert_eq!(tracked_descendants(&map, b"some"), 5);
1599 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1611 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1600 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1612 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1601 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1613 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1602
1614
1603 map.set_untracked(p(b"some/nested/file"))?;
1615 map.set_untracked(p(b"some/nested/file"))?;
1604 assert_eq!(map.len(), 5);
1616 assert_eq!(map.len(), 5);
1605 assert_eq!(tracked_descendants(&map, b"some"), 4);
1617 assert_eq!(tracked_descendants(&map, b"some"), 4);
1606 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1618 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1607 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1619 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1608 assert_does_not_exist(&map, b"some_nested");
1620 assert_does_not_exist(&map, b"some_nested");
1609
1621
1610 map.set_untracked(p(b"some/other/nested/path"))?;
1622 map.set_untracked(p(b"some/other/nested/path"))?;
1611 assert_eq!(map.len(), 4);
1623 assert_eq!(map.len(), 4);
1612 assert_eq!(tracked_descendants(&map, b"some"), 3);
1624 assert_eq!(tracked_descendants(&map, b"some"), 3);
1613 assert_does_not_exist(&map, b"some/other");
1625 assert_does_not_exist(&map, b"some/other");
1614
1626
1615 map.set_untracked(p(b"root_file"))?;
1627 map.set_untracked(p(b"root_file"))?;
1616 assert_eq!(map.len(), 3);
1628 assert_eq!(map.len(), 3);
1617 assert_eq!(tracked_descendants(&map, b"some"), 3);
1629 assert_eq!(tracked_descendants(&map, b"some"), 3);
1618 assert_does_not_exist(&map, b"root_file");
1630 assert_does_not_exist(&map, b"root_file");
1619
1631
1620 map.set_untracked(p(b"some/file"))?;
1632 map.set_untracked(p(b"some/file"))?;
1621 assert_eq!(map.len(), 2);
1633 assert_eq!(map.len(), 2);
1622 assert_eq!(tracked_descendants(&map, b"some"), 2);
1634 assert_eq!(tracked_descendants(&map, b"some"), 2);
1623 assert_does_not_exist(&map, b"some/file");
1635 assert_does_not_exist(&map, b"some/file");
1624
1636
1625 map.set_untracked(p(b"some/file2"))?;
1637 map.set_untracked(p(b"some/file2"))?;
1626 assert_eq!(map.len(), 1);
1638 assert_eq!(map.len(), 1);
1627 assert_eq!(tracked_descendants(&map, b"some"), 1);
1639 assert_eq!(tracked_descendants(&map, b"some"), 1);
1628 assert_does_not_exist(&map, b"some/file2");
1640 assert_does_not_exist(&map, b"some/file2");
1629
1641
1630 map.set_untracked(p(b"some/file3"))?;
1642 map.set_untracked(p(b"some/file3"))?;
1631 assert_eq!(map.len(), 0);
1643 assert_eq!(map.len(), 0);
1632 assert_does_not_exist(&map, b"some/file3");
1644 assert_does_not_exist(&map, b"some/file3");
1633
1645
1634 Ok(())
1646 Ok(())
1635 }
1647 }
1636
1648
1637 /// Check with a mix of tracked and non-tracked items
1649 /// Check with a mix of tracked and non-tracked items
1638 #[test]
1650 #[test]
1639 fn test_tracked_descendants_different() -> Result<(), DirstateError> {
1651 fn test_tracked_descendants_different() -> Result<(), DirstateError> {
1640 let mut map = OwningDirstateMap::new_empty(vec![]);
1652 let mut map = OwningDirstateMap::new_empty(vec![]);
1641
1653
1642 // A file that was just added
1654 // A file that was just added
1643 map.set_tracked(p(b"some/nested/path"))?;
1655 map.set_tracked(p(b"some/nested/path"))?;
1644 // This has no information, the dirstate should ignore it
1656 // This has no information, the dirstate should ignore it
1645 map.reset_state(p(b"some/file"), false, false, false, false, None)?;
1657 map.reset_state(p(b"some/file"), false, false, false, false, None)?;
1646 assert_does_not_exist(&map, b"some/file");
1658 assert_does_not_exist(&map, b"some/file");
1647
1659
1648 // A file that was removed
1660 // A file that was removed
1649 map.reset_state(
1661 map.reset_state(
1650 p(b"some/nested/file"),
1662 p(b"some/nested/file"),
1651 false,
1663 false,
1652 true,
1664 true,
1653 false,
1665 false,
1654 false,
1666 false,
1655 None,
1667 None,
1656 )?;
1668 )?;
1657 assert!(!map.get(p(b"some/nested/file"))?.unwrap().tracked());
1669 assert!(!map.get(p(b"some/nested/file"))?.unwrap().tracked());
1658 // Only present in p2
1670 // Only present in p2
1659 map.reset_state(p(b"some/file3"), false, false, true, false, None)?;
1671 map.reset_state(p(b"some/file3"), false, false, true, false, None)?;
1660 assert!(!map.get(p(b"some/file3"))?.unwrap().tracked());
1672 assert!(!map.get(p(b"some/file3"))?.unwrap().tracked());
1661 // A file that was merged
1673 // A file that was merged
1662 map.reset_state(p(b"root_file"), true, true, true, false, None)?;
1674 map.reset_state(p(b"root_file"), true, true, true, false, None)?;
1663 assert!(map.get(p(b"root_file"))?.unwrap().tracked());
1675 assert!(map.get(p(b"root_file"))?.unwrap().tracked());
1664 // A file that is added, with info from p2
1676 // A file that is added, with info from p2
1665 // XXX is that actually possible?
1677 // XXX is that actually possible?
1666 map.reset_state(p(b"some/file2"), true, false, true, false, None)?;
1678 map.reset_state(p(b"some/file2"), true, false, true, false, None)?;
1667 assert!(map.get(p(b"some/file2"))?.unwrap().tracked());
1679 assert!(map.get(p(b"some/file2"))?.unwrap().tracked());
1668 // A clean file
1680 // A clean file
1669 // One layer without any files to test deletion cascade
1681 // One layer without any files to test deletion cascade
1670 map.reset_state(
1682 map.reset_state(
1671 p(b"some/other/nested/path"),
1683 p(b"some/other/nested/path"),
1672 true,
1684 true,
1673 true,
1685 true,
1674 false,
1686 false,
1675 false,
1687 false,
1676 None,
1688 None,
1677 )?;
1689 )?;
1678 assert!(map.get(p(b"some/other/nested/path"))?.unwrap().tracked());
1690 assert!(map.get(p(b"some/other/nested/path"))?.unwrap().tracked());
1679
1691
1680 assert_eq!(map.len(), 6);
1692 assert_eq!(map.len(), 6);
1681 assert_eq!(tracked_descendants(&map, b"some"), 3);
1693 assert_eq!(tracked_descendants(&map, b"some"), 3);
1682 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1694 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1683 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1695 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1684 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1696 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1685 assert_eq!(tracked_descendants(&map, b"some/other/nested/path"), 0);
1697 assert_eq!(tracked_descendants(&map, b"some/other/nested/path"), 0);
1686 assert_eq!(
1698 assert_eq!(
1687 descendants_with_an_entry(&map, b"some/other/nested/path"),
1699 descendants_with_an_entry(&map, b"some/other/nested/path"),
1688 0
1700 0
1689 );
1701 );
1690 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1702 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1691 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1703 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1692
1704
1693 // might as well check this
1705 // might as well check this
1694 map.set_untracked(p(b"path/does/not/exist"))?;
1706 map.set_untracked(p(b"path/does/not/exist"))?;
1695 assert_eq!(map.len(), 6);
1707 assert_eq!(map.len(), 6);
1696
1708
1697 map.set_untracked(p(b"some/other/nested/path"))?;
1709 map.set_untracked(p(b"some/other/nested/path"))?;
1698 // It is set untracked but not deleted since it held other information
1710 // It is set untracked but not deleted since it held other information
1699 assert_eq!(map.len(), 6);
1711 assert_eq!(map.len(), 6);
1700 assert_eq!(tracked_descendants(&map, b"some"), 2);
1712 assert_eq!(tracked_descendants(&map, b"some"), 2);
1701 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1713 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1702 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1714 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1703 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1715 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1704 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1716 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1705 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1717 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1706
1718
1707 map.set_untracked(p(b"some/nested/path"))?;
1719 map.set_untracked(p(b"some/nested/path"))?;
1708 // It is set untracked *and* deleted since it was only added
1720 // It is set untracked *and* deleted since it was only added
1709 assert_eq!(map.len(), 5);
1721 assert_eq!(map.len(), 5);
1710 assert_eq!(tracked_descendants(&map, b"some"), 1);
1722 assert_eq!(tracked_descendants(&map, b"some"), 1);
1711 assert_eq!(descendants_with_an_entry(&map, b"some"), 4);
1723 assert_eq!(descendants_with_an_entry(&map, b"some"), 4);
1712 assert_eq!(tracked_descendants(&map, b"some/nested"), 0);
1724 assert_eq!(tracked_descendants(&map, b"some/nested"), 0);
1713 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 1);
1725 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 1);
1714 assert_does_not_exist(&map, b"some/nested/path");
1726 assert_does_not_exist(&map, b"some/nested/path");
1715
1727
1716 map.set_untracked(p(b"root_file"))?;
1728 map.set_untracked(p(b"root_file"))?;
1717 // Untracked but not deleted
1729 // Untracked but not deleted
1718 assert_eq!(map.len(), 5);
1730 assert_eq!(map.len(), 5);
1719 assert!(map.get(p(b"root_file"))?.is_some());
1731 assert!(map.get(p(b"root_file"))?.is_some());
1720
1732
1721 map.set_untracked(p(b"some/file2"))?;
1733 map.set_untracked(p(b"some/file2"))?;
1722 assert_eq!(map.len(), 5);
1734 assert_eq!(map.len(), 5);
1723 assert_eq!(tracked_descendants(&map, b"some"), 0);
1735 assert_eq!(tracked_descendants(&map, b"some"), 0);
1724 assert!(map.get(p(b"some/file2"))?.is_some());
1736 assert!(map.get(p(b"some/file2"))?.is_some());
1725
1737
1726 map.set_untracked(p(b"some/file3"))?;
1738 map.set_untracked(p(b"some/file3"))?;
1727 assert_eq!(map.len(), 5);
1739 assert_eq!(map.len(), 5);
1728 assert_eq!(tracked_descendants(&map, b"some"), 0);
1740 assert_eq!(tracked_descendants(&map, b"some"), 0);
1729 assert!(map.get(p(b"some/file3"))?.is_some());
1741 assert!(map.get(p(b"some/file3"))?.is_some());
1730
1742
1731 Ok(())
1743 Ok(())
1732 }
1744 }
1733
1745
1734 /// Check that copies counter is correctly updated
1746 /// Check that copies counter is correctly updated
1735 #[test]
1747 #[test]
1736 fn test_copy_source() -> Result<(), DirstateError> {
1748 fn test_copy_source() -> Result<(), DirstateError> {
1737 let mut map = OwningDirstateMap::new_empty(vec![]);
1749 let mut map = OwningDirstateMap::new_empty(vec![]);
1738
1750
1739 // Clean file
1751 // Clean file
1740 map.reset_state(p(b"files/clean"), true, true, false, false, None)?;
1752 map.reset_state(p(b"files/clean"), true, true, false, false, None)?;
1741 // Merged file
1753 // Merged file
1742 map.reset_state(p(b"files/from_p2"), true, true, true, false, None)?;
1754 map.reset_state(p(b"files/from_p2"), true, true, true, false, None)?;
1743 // Removed file
1755 // Removed file
1744 map.reset_state(p(b"removed"), false, true, false, false, None)?;
1756 map.reset_state(p(b"removed"), false, true, false, false, None)?;
1745 // Added file
1757 // Added file
1746 map.reset_state(p(b"files/added"), true, false, false, false, None)?;
1758 map.reset_state(p(b"files/added"), true, false, false, false, None)?;
1747 // Add copy
1759 // Add copy
1748 map.copy_map_insert(p(b"files/clean"), p(b"clean_copy_source"))?;
1760 map.copy_map_insert(p(b"files/clean"), p(b"clean_copy_source"))?;
1749 assert_eq!(map.copy_map_len(), 1);
1761 assert_eq!(map.copy_map_len(), 1);
1750
1762
1751 // Copy override
1763 // Copy override
1752 map.copy_map_insert(p(b"files/clean"), p(b"other_clean_copy_source"))?;
1764 map.copy_map_insert(p(b"files/clean"), p(b"other_clean_copy_source"))?;
1753 assert_eq!(map.copy_map_len(), 1);
1765 assert_eq!(map.copy_map_len(), 1);
1754
1766
1755 // Multiple copies
1767 // Multiple copies
1756 map.copy_map_insert(p(b"removed"), p(b"removed_copy_source"))?;
1768 map.copy_map_insert(p(b"removed"), p(b"removed_copy_source"))?;
1757 assert_eq!(map.copy_map_len(), 2);
1769 assert_eq!(map.copy_map_len(), 2);
1758
1770
1759 map.copy_map_insert(p(b"files/added"), p(b"added_copy_source"))?;
1771 map.copy_map_insert(p(b"files/added"), p(b"added_copy_source"))?;
1760 assert_eq!(map.copy_map_len(), 3);
1772 assert_eq!(map.copy_map_len(), 3);
1761
1773
1762 // Added, so the entry is completely removed
1774 // Added, so the entry is completely removed
1763 map.set_untracked(p(b"files/added"))?;
1775 map.set_untracked(p(b"files/added"))?;
1764 assert_does_not_exist(&map, b"files/added");
1776 assert_does_not_exist(&map, b"files/added");
1765 assert_eq!(map.copy_map_len(), 2);
1777 assert_eq!(map.copy_map_len(), 2);
1766
1778
1767 // Removed, so the entry is kept around, so is its copy
1779 // Removed, so the entry is kept around, so is its copy
1768 map.set_untracked(p(b"removed"))?;
1780 map.set_untracked(p(b"removed"))?;
1769 assert!(map.get(p(b"removed"))?.is_some());
1781 assert!(map.get(p(b"removed"))?.is_some());
1770 assert_eq!(map.copy_map_len(), 2);
1782 assert_eq!(map.copy_map_len(), 2);
1771
1783
1772 // Clean, so the entry is kept around, but not its copy
1784 // Clean, so the entry is kept around, but not its copy
1773 map.set_untracked(p(b"files/clean"))?;
1785 map.set_untracked(p(b"files/clean"))?;
1774 assert!(map.get(p(b"files/clean"))?.is_some());
1786 assert!(map.get(p(b"files/clean"))?.is_some());
1775 assert_eq!(map.copy_map_len(), 1);
1787 assert_eq!(map.copy_map_len(), 1);
1776
1788
1777 map.copy_map_insert(p(b"files/from_p2"), p(b"from_p2_copy_source"))?;
1789 map.copy_map_insert(p(b"files/from_p2"), p(b"from_p2_copy_source"))?;
1778 assert_eq!(map.copy_map_len(), 2);
1790 assert_eq!(map.copy_map_len(), 2);
1779
1791
1780 // Info from p2, so its copy source info is kept around
1792 // Info from p2, so its copy source info is kept around
1781 map.set_untracked(p(b"files/from_p2"))?;
1793 map.set_untracked(p(b"files/from_p2"))?;
1782 assert!(map.get(p(b"files/from_p2"))?.is_some());
1794 assert!(map.get(p(b"files/from_p2"))?.is_some());
1783 assert_eq!(map.copy_map_len(), 2);
1795 assert_eq!(map.copy_map_len(), 2);
1784
1796
1785 Ok(())
1797 Ok(())
1786 }
1798 }
1787
1799
1788 /// Test with "on disk" data. For the sake of this test, the "on disk" data
1800 /// Test with "on disk" data. For the sake of this test, the "on disk" data
1789 /// does not actually come from the disk, but it's opaque to the code being
1801 /// does not actually come from the disk, but it's opaque to the code being
1790 /// tested.
1802 /// tested.
1791 #[test]
1803 #[test]
1792 fn test_on_disk() -> Result<(), DirstateError> {
1804 fn test_on_disk() -> Result<(), DirstateError> {
1793 // First let's create some data to put "on disk"
1805 // First let's create some data to put "on disk"
1794 let mut map = OwningDirstateMap::new_empty(vec![]);
1806 let mut map = OwningDirstateMap::new_empty(vec![]);
1795
1807
1796 // A file that was just added
1808 // A file that was just added
1797 map.set_tracked(p(b"some/nested/added"))?;
1809 map.set_tracked(p(b"some/nested/added"))?;
1798 map.copy_map_insert(p(b"some/nested/added"), p(b"added_copy_source"))?;
1810 map.copy_map_insert(p(b"some/nested/added"), p(b"added_copy_source"))?;
1799
1811
1800 // A file that was removed
1812 // A file that was removed
1801 map.reset_state(
1813 map.reset_state(
1802 p(b"some/nested/removed"),
1814 p(b"some/nested/removed"),
1803 false,
1815 false,
1804 true,
1816 true,
1805 false,
1817 false,
1806 false,
1818 false,
1807 None,
1819 None,
1808 )?;
1820 )?;
1809 // Only present in p2
1821 // Only present in p2
1810 map.reset_state(
1822 map.reset_state(
1811 p(b"other/p2_info_only"),
1823 p(b"other/p2_info_only"),
1812 false,
1824 false,
1813 false,
1825 false,
1814 true,
1826 true,
1815 false,
1827 false,
1816 None,
1828 None,
1817 )?;
1829 )?;
1818 map.copy_map_insert(
1830 map.copy_map_insert(
1819 p(b"other/p2_info_only"),
1831 p(b"other/p2_info_only"),
1820 p(b"other/p2_info_copy_source"),
1832 p(b"other/p2_info_copy_source"),
1821 )?;
1833 )?;
1822 // A file that was merged
1834 // A file that was merged
1823 map.reset_state(p(b"merged"), true, true, true, false, None)?;
1835 map.reset_state(p(b"merged"), true, true, true, false, None)?;
1824 // A file that is added, with info from p2
1836 // A file that is added, with info from p2
1825 // XXX is that actually possible?
1837 // XXX is that actually possible?
1826 map.reset_state(
1838 map.reset_state(
1827 p(b"other/added_with_p2"),
1839 p(b"other/added_with_p2"),
1828 true,
1840 true,
1829 false,
1841 false,
1830 true,
1842 true,
1831 false,
1843 false,
1832 None,
1844 None,
1833 )?;
1845 )?;
1834 // One layer without any files to test deletion cascade
1846 // One layer without any files to test deletion cascade
1835 // A clean file
1847 // A clean file
1836 map.reset_state(
1848 map.reset_state(
1837 p(b"some/other/nested/clean"),
1849 p(b"some/other/nested/clean"),
1838 true,
1850 true,
1839 true,
1851 true,
1840 false,
1852 false,
1841 false,
1853 false,
1842 None,
1854 None,
1843 )?;
1855 )?;
1844
1856
1845 let (packed, metadata, _should_append, _old_data_size) =
1857 let (packed, metadata, _should_append, _old_data_size) =
1846 map.pack_v2(DirstateMapWriteMode::ForceNewDataFile)?;
1858 map.pack_v2(DirstateMapWriteMode::ForceNewDataFile)?;
1847 let packed_len = packed.len();
1859 let packed_len = packed.len();
1848 assert!(packed_len > 0);
1860 assert!(packed_len > 0);
1849
1861
1850 // Recreate "from disk"
1862 // Recreate "from disk"
1851 let mut map = OwningDirstateMap::new_v2(
1863 let mut map = OwningDirstateMap::new_v2(
1852 packed,
1864 packed,
1853 packed_len,
1865 packed_len,
1854 metadata.as_bytes(),
1866 metadata.as_bytes(),
1855 vec![],
1867 vec![],
1868 None,
1856 )?;
1869 )?;
1857
1870
1858 // Check that everything is accounted for
1871 // Check that everything is accounted for
1859 assert!(map.contains_key(p(b"some/nested/added"))?);
1872 assert!(map.contains_key(p(b"some/nested/added"))?);
1860 assert!(map.contains_key(p(b"some/nested/removed"))?);
1873 assert!(map.contains_key(p(b"some/nested/removed"))?);
1861 assert!(map.contains_key(p(b"merged"))?);
1874 assert!(map.contains_key(p(b"merged"))?);
1862 assert!(map.contains_key(p(b"other/p2_info_only"))?);
1875 assert!(map.contains_key(p(b"other/p2_info_only"))?);
1863 assert!(map.contains_key(p(b"other/added_with_p2"))?);
1876 assert!(map.contains_key(p(b"other/added_with_p2"))?);
1864 assert!(map.contains_key(p(b"some/other/nested/clean"))?);
1877 assert!(map.contains_key(p(b"some/other/nested/clean"))?);
1865 assert_eq!(
1878 assert_eq!(
1866 map.copy_map_get(p(b"some/nested/added"))?,
1879 map.copy_map_get(p(b"some/nested/added"))?,
1867 Some(p(b"added_copy_source"))
1880 Some(p(b"added_copy_source"))
1868 );
1881 );
1869 assert_eq!(
1882 assert_eq!(
1870 map.copy_map_get(p(b"other/p2_info_only"))?,
1883 map.copy_map_get(p(b"other/p2_info_only"))?,
1871 Some(p(b"other/p2_info_copy_source"))
1884 Some(p(b"other/p2_info_copy_source"))
1872 );
1885 );
1873 assert_eq!(tracked_descendants(&map, b"some"), 2);
1886 assert_eq!(tracked_descendants(&map, b"some"), 2);
1874 assert_eq!(descendants_with_an_entry(&map, b"some"), 3);
1887 assert_eq!(descendants_with_an_entry(&map, b"some"), 3);
1875 assert_eq!(tracked_descendants(&map, b"other"), 1);
1888 assert_eq!(tracked_descendants(&map, b"other"), 1);
1876 assert_eq!(descendants_with_an_entry(&map, b"other"), 2);
1889 assert_eq!(descendants_with_an_entry(&map, b"other"), 2);
1877 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1890 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1878 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1891 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1879 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1892 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1880 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1893 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1881 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1894 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1882 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1895 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1883 assert_eq!(map.len(), 6);
1896 assert_eq!(map.len(), 6);
1884 assert_eq!(map.get_map().unreachable_bytes, 0);
1897 assert_eq!(map.get_map().unreachable_bytes, 0);
1885 assert_eq!(map.copy_map_len(), 2);
1898 assert_eq!(map.copy_map_len(), 2);
1886
1899
1887 // Shouldn't change anything since it's already not tracked
1900 // Shouldn't change anything since it's already not tracked
1888 map.set_untracked(p(b"some/nested/removed"))?;
1901 map.set_untracked(p(b"some/nested/removed"))?;
1889 assert_eq!(map.get_map().unreachable_bytes, 0);
1902 assert_eq!(map.get_map().unreachable_bytes, 0);
1890
1903
1891 match map.get_map().root {
1904 match map.get_map().root {
1892 ChildNodes::InMemory(_) => {
1905 ChildNodes::InMemory(_) => {
1893 panic!("root should not have been mutated")
1906 panic!("root should not have been mutated")
1894 }
1907 }
1895 _ => (),
1908 _ => (),
1896 }
1909 }
1897 // We haven't mutated enough (nothing, actually), we should still be in
1910 // We haven't mutated enough (nothing, actually), we should still be in
1898 // the append strategy
1911 // the append strategy
1899 assert!(map.get_map().write_should_append());
1912 assert!(map.get_map().write_should_append());
1900
1913
1901 // But this mutates the structure, so there should be unreachable_bytes
1914 // But this mutates the structure, so there should be unreachable_bytes
1902 assert!(map.set_untracked(p(b"some/nested/added"))?);
1915 assert!(map.set_untracked(p(b"some/nested/added"))?);
1903 let unreachable_bytes = map.get_map().unreachable_bytes;
1916 let unreachable_bytes = map.get_map().unreachable_bytes;
1904 assert!(unreachable_bytes > 0);
1917 assert!(unreachable_bytes > 0);
1905
1918
1906 match map.get_map().root {
1919 match map.get_map().root {
1907 ChildNodes::OnDisk(_) => panic!("root should have been mutated"),
1920 ChildNodes::OnDisk(_) => panic!("root should have been mutated"),
1908 _ => (),
1921 _ => (),
1909 }
1922 }
1910
1923
1911 // This should not mutate the structure either, since `root` has
1924 // This should not mutate the structure either, since `root` has
1912 // already been mutated along with its direct children.
1925 // already been mutated along with its direct children.
1913 map.set_untracked(p(b"merged"))?;
1926 map.set_untracked(p(b"merged"))?;
1914 assert_eq!(map.get_map().unreachable_bytes, unreachable_bytes);
1927 assert_eq!(map.get_map().unreachable_bytes, unreachable_bytes);
1915
1928
1916 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1929 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1917 NodeRef::InMemory(_, _) => {
1930 NodeRef::InMemory(_, _) => {
1918 panic!("'other/added_with_p2' should not have been mutated")
1931 panic!("'other/added_with_p2' should not have been mutated")
1919 }
1932 }
1920 _ => (),
1933 _ => (),
1921 }
1934 }
1922 // But this should, since it's in a different path
1935 // But this should, since it's in a different path
1923 // than `<root>some/nested/add`
1936 // than `<root>some/nested/add`
1924 map.set_untracked(p(b"other/added_with_p2"))?;
1937 map.set_untracked(p(b"other/added_with_p2"))?;
1925 assert!(map.get_map().unreachable_bytes > unreachable_bytes);
1938 assert!(map.get_map().unreachable_bytes > unreachable_bytes);
1926
1939
1927 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1940 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1928 NodeRef::OnDisk(_) => {
1941 NodeRef::OnDisk(_) => {
1929 panic!("'other/added_with_p2' should have been mutated")
1942 panic!("'other/added_with_p2' should have been mutated")
1930 }
1943 }
1931 _ => (),
1944 _ => (),
1932 }
1945 }
1933
1946
1934 // We have rewritten most of the tree, we should create a new file
1947 // We have rewritten most of the tree, we should create a new file
1935 assert!(!map.get_map().write_should_append());
1948 assert!(!map.get_map().write_should_append());
1936
1949
1937 Ok(())
1950 Ok(())
1938 }
1951 }
1939 }
1952 }
@@ -1,892 +1,894 b''
1 //! The "version 2" disk representation of the dirstate
1 //! The "version 2" disk representation of the dirstate
2 //!
2 //!
3 //! See `mercurial/helptext/internals/dirstate-v2.txt`
3 //! See `mercurial/helptext/internals/dirstate-v2.txt`
4
4
5 use crate::dirstate::{DirstateV2Data, TruncatedTimestamp};
5 use crate::dirstate::{DirstateV2Data, TruncatedTimestamp};
6 use crate::dirstate_tree::dirstate_map::DirstateVersion;
6 use crate::dirstate_tree::dirstate_map::DirstateVersion;
7 use crate::dirstate_tree::dirstate_map::{
7 use crate::dirstate_tree::dirstate_map::{
8 self, DirstateMap, DirstateMapWriteMode, NodeRef,
8 self, DirstateMap, DirstateMapWriteMode, NodeRef,
9 };
9 };
10 use crate::dirstate_tree::path_with_basename::WithBasename;
10 use crate::dirstate_tree::path_with_basename::WithBasename;
11 use crate::errors::HgError;
11 use crate::errors::HgError;
12 use crate::utils::hg_path::HgPath;
12 use crate::utils::hg_path::HgPath;
13 use crate::DirstateEntry;
13 use crate::DirstateEntry;
14 use crate::DirstateError;
14 use crate::DirstateError;
15 use crate::DirstateParents;
15 use crate::DirstateParents;
16 use bitflags::bitflags;
16 use bitflags::bitflags;
17 use bytes_cast::unaligned::{U16Be, U32Be};
17 use bytes_cast::unaligned::{U16Be, U32Be};
18 use bytes_cast::BytesCast;
18 use bytes_cast::BytesCast;
19 use format_bytes::format_bytes;
19 use format_bytes::format_bytes;
20 use rand::Rng;
20 use rand::Rng;
21 use std::borrow::Cow;
21 use std::borrow::Cow;
22 use std::convert::{TryFrom, TryInto};
22 use std::convert::{TryFrom, TryInto};
23 use std::fmt::Write;
23 use std::fmt::Write;
24
24
25 /// Added at the start of `.hg/dirstate` when the "v2" format is used.
25 /// Added at the start of `.hg/dirstate` when the "v2" format is used.
26 /// This a redundant sanity check more than an actual "magic number" since
26 /// This a redundant sanity check more than an actual "magic number" since
27 /// `.hg/requires` already governs which format should be used.
27 /// `.hg/requires` already governs which format should be used.
28 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
28 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
29
29
30 /// Keep space for 256-bit hashes
30 /// Keep space for 256-bit hashes
31 const STORED_NODE_ID_BYTES: usize = 32;
31 const STORED_NODE_ID_BYTES: usize = 32;
32
32
33 /// … even though only 160 bits are used for now, with SHA-1
33 /// … even though only 160 bits are used for now, with SHA-1
34 const USED_NODE_ID_BYTES: usize = 20;
34 const USED_NODE_ID_BYTES: usize = 20;
35
35
36 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
36 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
37 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
37 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
38
38
39 /// Must match constants of the same names in `mercurial/dirstateutils/v2.py`
39 /// Must match constants of the same names in `mercurial/dirstateutils/v2.py`
40 const TREE_METADATA_SIZE: usize = 44;
40 const TREE_METADATA_SIZE: usize = 44;
41 const NODE_SIZE: usize = 44;
41 const NODE_SIZE: usize = 44;
42
42
43 /// Make sure that size-affecting changes are made knowingly
43 /// Make sure that size-affecting changes are made knowingly
44 #[allow(unused)]
44 #[allow(unused)]
45 fn static_assert_size_of() {
45 fn static_assert_size_of() {
46 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
46 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
47 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
47 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
48 let _ = std::mem::transmute::<Node, [u8; NODE_SIZE]>;
48 let _ = std::mem::transmute::<Node, [u8; NODE_SIZE]>;
49 }
49 }
50
50
51 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
51 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
52 #[derive(BytesCast)]
52 #[derive(BytesCast)]
53 #[repr(C)]
53 #[repr(C)]
54 struct DocketHeader {
54 struct DocketHeader {
55 marker: [u8; V2_FORMAT_MARKER.len()],
55 marker: [u8; V2_FORMAT_MARKER.len()],
56 parent_1: [u8; STORED_NODE_ID_BYTES],
56 parent_1: [u8; STORED_NODE_ID_BYTES],
57 parent_2: [u8; STORED_NODE_ID_BYTES],
57 parent_2: [u8; STORED_NODE_ID_BYTES],
58
58
59 metadata: TreeMetadata,
59 metadata: TreeMetadata,
60
60
61 /// Counted in bytes
61 /// Counted in bytes
62 data_size: Size,
62 data_size: Size,
63
63
64 uuid_size: u8,
64 uuid_size: u8,
65 }
65 }
66
66
67 pub struct Docket<'on_disk> {
67 pub struct Docket<'on_disk> {
68 header: &'on_disk DocketHeader,
68 header: &'on_disk DocketHeader,
69 pub uuid: &'on_disk [u8],
69 pub uuid: &'on_disk [u8],
70 }
70 }
71
71
72 /// Fields are documented in the *Tree metadata in the docket file*
72 /// Fields are documented in the *Tree metadata in the docket file*
73 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
73 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
74 #[derive(BytesCast)]
74 #[derive(BytesCast)]
75 #[repr(C)]
75 #[repr(C)]
76 pub struct TreeMetadata {
76 pub struct TreeMetadata {
77 root_nodes: ChildNodes,
77 root_nodes: ChildNodes,
78 nodes_with_entry_count: Size,
78 nodes_with_entry_count: Size,
79 nodes_with_copy_source_count: Size,
79 nodes_with_copy_source_count: Size,
80 unreachable_bytes: Size,
80 unreachable_bytes: Size,
81 unused: [u8; 4],
81 unused: [u8; 4],
82
82
83 /// See *Optional hash of ignore patterns* section of
83 /// See *Optional hash of ignore patterns* section of
84 /// `mercurial/helptext/internals/dirstate-v2.txt`
84 /// `mercurial/helptext/internals/dirstate-v2.txt`
85 ignore_patterns_hash: IgnorePatternsHash,
85 ignore_patterns_hash: IgnorePatternsHash,
86 }
86 }
87
87
88 /// Fields are documented in the *The data file format*
88 /// Fields are documented in the *The data file format*
89 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
89 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
90 #[derive(BytesCast, Debug)]
90 #[derive(BytesCast, Debug)]
91 #[repr(C)]
91 #[repr(C)]
92 pub(super) struct Node {
92 pub(super) struct Node {
93 full_path: PathSlice,
93 full_path: PathSlice,
94
94
95 /// In bytes from `self.full_path.start`
95 /// In bytes from `self.full_path.start`
96 base_name_start: PathSize,
96 base_name_start: PathSize,
97
97
98 copy_source: OptPathSlice,
98 copy_source: OptPathSlice,
99 children: ChildNodes,
99 children: ChildNodes,
100 pub(super) descendants_with_entry_count: Size,
100 pub(super) descendants_with_entry_count: Size,
101 pub(super) tracked_descendants_count: Size,
101 pub(super) tracked_descendants_count: Size,
102 flags: U16Be,
102 flags: U16Be,
103 size: U32Be,
103 size: U32Be,
104 mtime: PackedTruncatedTimestamp,
104 mtime: PackedTruncatedTimestamp,
105 }
105 }
106
106
107 bitflags! {
107 bitflags! {
108 #[repr(C)]
108 #[repr(C)]
109 struct Flags: u16 {
109 struct Flags: u16 {
110 const WDIR_TRACKED = 1 << 0;
110 const WDIR_TRACKED = 1 << 0;
111 const P1_TRACKED = 1 << 1;
111 const P1_TRACKED = 1 << 1;
112 const P2_INFO = 1 << 2;
112 const P2_INFO = 1 << 2;
113 const MODE_EXEC_PERM = 1 << 3;
113 const MODE_EXEC_PERM = 1 << 3;
114 const MODE_IS_SYMLINK = 1 << 4;
114 const MODE_IS_SYMLINK = 1 << 4;
115 const HAS_FALLBACK_EXEC = 1 << 5;
115 const HAS_FALLBACK_EXEC = 1 << 5;
116 const FALLBACK_EXEC = 1 << 6;
116 const FALLBACK_EXEC = 1 << 6;
117 const HAS_FALLBACK_SYMLINK = 1 << 7;
117 const HAS_FALLBACK_SYMLINK = 1 << 7;
118 const FALLBACK_SYMLINK = 1 << 8;
118 const FALLBACK_SYMLINK = 1 << 8;
119 const EXPECTED_STATE_IS_MODIFIED = 1 << 9;
119 const EXPECTED_STATE_IS_MODIFIED = 1 << 9;
120 const HAS_MODE_AND_SIZE = 1 <<10;
120 const HAS_MODE_AND_SIZE = 1 <<10;
121 const HAS_MTIME = 1 <<11;
121 const HAS_MTIME = 1 <<11;
122 const MTIME_SECOND_AMBIGUOUS = 1 << 12;
122 const MTIME_SECOND_AMBIGUOUS = 1 << 12;
123 const DIRECTORY = 1 <<13;
123 const DIRECTORY = 1 <<13;
124 const ALL_UNKNOWN_RECORDED = 1 <<14;
124 const ALL_UNKNOWN_RECORDED = 1 <<14;
125 const ALL_IGNORED_RECORDED = 1 <<15;
125 const ALL_IGNORED_RECORDED = 1 <<15;
126 }
126 }
127 }
127 }
128
128
129 /// Duration since the Unix epoch
129 /// Duration since the Unix epoch
130 #[derive(BytesCast, Copy, Clone, Debug)]
130 #[derive(BytesCast, Copy, Clone, Debug)]
131 #[repr(C)]
131 #[repr(C)]
132 struct PackedTruncatedTimestamp {
132 struct PackedTruncatedTimestamp {
133 truncated_seconds: U32Be,
133 truncated_seconds: U32Be,
134 nanoseconds: U32Be,
134 nanoseconds: U32Be,
135 }
135 }
136
136
137 /// Counted in bytes from the start of the file
137 /// Counted in bytes from the start of the file
138 ///
138 ///
139 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
139 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
140 type Offset = U32Be;
140 type Offset = U32Be;
141
141
142 /// Counted in number of items
142 /// Counted in number of items
143 ///
143 ///
144 /// NOTE: we choose not to support counting more than 4 billion nodes anywhere.
144 /// NOTE: we choose not to support counting more than 4 billion nodes anywhere.
145 type Size = U32Be;
145 type Size = U32Be;
146
146
147 /// Counted in bytes
147 /// Counted in bytes
148 ///
148 ///
149 /// NOTE: we choose not to support file names/paths longer than 64 KiB.
149 /// NOTE: we choose not to support file names/paths longer than 64 KiB.
150 type PathSize = U16Be;
150 type PathSize = U16Be;
151
151
152 /// A contiguous sequence of `len` times `Node`, representing the child nodes
152 /// A contiguous sequence of `len` times `Node`, representing the child nodes
153 /// of either some other node or of the repository root.
153 /// of either some other node or of the repository root.
154 ///
154 ///
155 /// Always sorted by ascending `full_path`, to allow binary search.
155 /// Always sorted by ascending `full_path`, to allow binary search.
156 /// Since nodes with the same parent nodes also have the same parent path,
156 /// Since nodes with the same parent nodes also have the same parent path,
157 /// only the `base_name`s need to be compared during binary search.
157 /// only the `base_name`s need to be compared during binary search.
158 #[derive(BytesCast, Copy, Clone, Debug)]
158 #[derive(BytesCast, Copy, Clone, Debug)]
159 #[repr(C)]
159 #[repr(C)]
160 struct ChildNodes {
160 struct ChildNodes {
161 start: Offset,
161 start: Offset,
162 len: Size,
162 len: Size,
163 }
163 }
164
164
165 /// A `HgPath` of `len` bytes
165 /// A `HgPath` of `len` bytes
166 #[derive(BytesCast, Copy, Clone, Debug)]
166 #[derive(BytesCast, Copy, Clone, Debug)]
167 #[repr(C)]
167 #[repr(C)]
168 struct PathSlice {
168 struct PathSlice {
169 start: Offset,
169 start: Offset,
170 len: PathSize,
170 len: PathSize,
171 }
171 }
172
172
173 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
173 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
174 type OptPathSlice = PathSlice;
174 type OptPathSlice = PathSlice;
175
175
176 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
176 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
177 ///
177 ///
178 /// This should only happen if Mercurial is buggy or a repository is corrupted.
178 /// This should only happen if Mercurial is buggy or a repository is corrupted.
179 #[derive(Debug)]
179 #[derive(Debug)]
180 pub struct DirstateV2ParseError {
180 pub struct DirstateV2ParseError {
181 message: String,
181 message: String,
182 }
182 }
183
183
184 impl DirstateV2ParseError {
184 impl DirstateV2ParseError {
185 pub fn new<S: Into<String>>(message: S) -> Self {
185 pub fn new<S: Into<String>>(message: S) -> Self {
186 Self {
186 Self {
187 message: message.into(),
187 message: message.into(),
188 }
188 }
189 }
189 }
190 }
190 }
191
191
192 impl From<DirstateV2ParseError> for HgError {
192 impl From<DirstateV2ParseError> for HgError {
193 fn from(e: DirstateV2ParseError) -> Self {
193 fn from(e: DirstateV2ParseError) -> Self {
194 HgError::corrupted(format!("dirstate-v2 parse error: {}", e.message))
194 HgError::corrupted(format!("dirstate-v2 parse error: {}", e.message))
195 }
195 }
196 }
196 }
197
197
198 impl From<DirstateV2ParseError> for crate::DirstateError {
198 impl From<DirstateV2ParseError> for crate::DirstateError {
199 fn from(error: DirstateV2ParseError) -> Self {
199 fn from(error: DirstateV2ParseError) -> Self {
200 HgError::from(error).into()
200 HgError::from(error).into()
201 }
201 }
202 }
202 }
203
203
204 impl TreeMetadata {
204 impl TreeMetadata {
205 pub fn as_bytes(&self) -> &[u8] {
205 pub fn as_bytes(&self) -> &[u8] {
206 BytesCast::as_bytes(self)
206 BytesCast::as_bytes(self)
207 }
207 }
208 }
208 }
209
209
210 impl<'on_disk> Docket<'on_disk> {
210 impl<'on_disk> Docket<'on_disk> {
211 /// Generate the identifier for a new data file
211 /// Generate the identifier for a new data file
212 ///
212 ///
213 /// TODO: support the `HGTEST_UUIDFILE` environment variable.
213 /// TODO: support the `HGTEST_UUIDFILE` environment variable.
214 /// See `mercurial/revlogutils/docket.py`
214 /// See `mercurial/revlogutils/docket.py`
215 pub fn new_uid() -> String {
215 pub fn new_uid() -> String {
216 const ID_LENGTH: usize = 8;
216 const ID_LENGTH: usize = 8;
217 let mut id = String::with_capacity(ID_LENGTH);
217 let mut id = String::with_capacity(ID_LENGTH);
218 let mut rng = rand::thread_rng();
218 let mut rng = rand::thread_rng();
219 for _ in 0..ID_LENGTH {
219 for _ in 0..ID_LENGTH {
220 // One random hexadecimal digit.
220 // One random hexadecimal digit.
221 // `unwrap` never panics because `impl Write for String`
221 // `unwrap` never panics because `impl Write for String`
222 // never returns an error.
222 // never returns an error.
223 write!(&mut id, "{:x}", rng.gen_range(0..16)).unwrap();
223 write!(&mut id, "{:x}", rng.gen_range(0..16)).unwrap();
224 }
224 }
225 id
225 id
226 }
226 }
227
227
228 pub fn serialize(
228 pub fn serialize(
229 parents: DirstateParents,
229 parents: DirstateParents,
230 tree_metadata: TreeMetadata,
230 tree_metadata: TreeMetadata,
231 data_size: u64,
231 data_size: u64,
232 uuid: &[u8],
232 uuid: &[u8],
233 ) -> Result<Vec<u8>, std::num::TryFromIntError> {
233 ) -> Result<Vec<u8>, std::num::TryFromIntError> {
234 let header = DocketHeader {
234 let header = DocketHeader {
235 marker: *V2_FORMAT_MARKER,
235 marker: *V2_FORMAT_MARKER,
236 parent_1: parents.p1.pad_to_256_bits(),
236 parent_1: parents.p1.pad_to_256_bits(),
237 parent_2: parents.p2.pad_to_256_bits(),
237 parent_2: parents.p2.pad_to_256_bits(),
238 metadata: tree_metadata,
238 metadata: tree_metadata,
239 data_size: u32::try_from(data_size)?.into(),
239 data_size: u32::try_from(data_size)?.into(),
240 uuid_size: uuid.len().try_into()?,
240 uuid_size: uuid.len().try_into()?,
241 };
241 };
242 let header = header.as_bytes();
242 let header = header.as_bytes();
243 let mut docket = Vec::with_capacity(header.len() + uuid.len());
243 let mut docket = Vec::with_capacity(header.len() + uuid.len());
244 docket.extend_from_slice(header);
244 docket.extend_from_slice(header);
245 docket.extend_from_slice(uuid);
245 docket.extend_from_slice(uuid);
246 Ok(docket)
246 Ok(docket)
247 }
247 }
248
248
249 pub fn parents(&self) -> DirstateParents {
249 pub fn parents(&self) -> DirstateParents {
250 use crate::Node;
250 use crate::Node;
251 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
251 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
252 .unwrap()
252 .unwrap()
253 .clone();
253 .clone();
254 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
254 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
255 .unwrap()
255 .unwrap()
256 .clone();
256 .clone();
257 DirstateParents { p1, p2 }
257 DirstateParents { p1, p2 }
258 }
258 }
259
259
260 pub fn tree_metadata(&self) -> &[u8] {
260 pub fn tree_metadata(&self) -> &[u8] {
261 self.header.metadata.as_bytes()
261 self.header.metadata.as_bytes()
262 }
262 }
263
263
264 pub fn data_size(&self) -> usize {
264 pub fn data_size(&self) -> usize {
265 // This `unwrap` could only panic on a 16-bit CPU
265 // This `unwrap` could only panic on a 16-bit CPU
266 self.header.data_size.get().try_into().unwrap()
266 self.header.data_size.get().try_into().unwrap()
267 }
267 }
268
268
269 pub fn data_filename(&self) -> String {
269 pub fn data_filename(&self) -> String {
270 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
270 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
271 }
271 }
272 }
272 }
273
273
274 pub fn read_docket(
274 pub fn read_docket(
275 on_disk: &[u8],
275 on_disk: &[u8],
276 ) -> Result<Docket<'_>, DirstateV2ParseError> {
276 ) -> Result<Docket<'_>, DirstateV2ParseError> {
277 let (header, uuid) = DocketHeader::from_bytes(on_disk).map_err(|e| {
277 let (header, uuid) = DocketHeader::from_bytes(on_disk).map_err(|e| {
278 DirstateV2ParseError::new(format!("when reading docket, {}", e))
278 DirstateV2ParseError::new(format!("when reading docket, {}", e))
279 })?;
279 })?;
280 let uuid_size = header.uuid_size as usize;
280 let uuid_size = header.uuid_size as usize;
281 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
281 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
282 Ok(Docket { header, uuid })
282 Ok(Docket { header, uuid })
283 } else {
283 } else {
284 Err(DirstateV2ParseError::new(
284 Err(DirstateV2ParseError::new(
285 "invalid format marker or uuid size",
285 "invalid format marker or uuid size",
286 ))
286 ))
287 }
287 }
288 }
288 }
289
289
290 pub(super) fn read<'on_disk>(
290 pub(super) fn read<'on_disk>(
291 on_disk: &'on_disk [u8],
291 on_disk: &'on_disk [u8],
292 metadata: &[u8],
292 metadata: &[u8],
293 uuid: Vec<u8>,
293 uuid: Vec<u8>,
294 identity: Option<u64>,
294 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
295 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
295 if on_disk.is_empty() {
296 if on_disk.is_empty() {
296 let mut map = DirstateMap::empty(on_disk);
297 let mut map = DirstateMap::empty(on_disk);
297 map.dirstate_version = DirstateVersion::V2;
298 map.dirstate_version = DirstateVersion::V2;
298 return Ok(map);
299 return Ok(map);
299 }
300 }
300 let (meta, _) = TreeMetadata::from_bytes(metadata).map_err(|e| {
301 let (meta, _) = TreeMetadata::from_bytes(metadata).map_err(|e| {
301 DirstateV2ParseError::new(format!("when parsing tree metadata, {}", e))
302 DirstateV2ParseError::new(format!("when parsing tree metadata, {}", e))
302 })?;
303 })?;
303 let dirstate_map = DirstateMap {
304 let dirstate_map = DirstateMap {
304 on_disk,
305 on_disk,
305 root: dirstate_map::ChildNodes::OnDisk(
306 root: dirstate_map::ChildNodes::OnDisk(
306 read_nodes(on_disk, meta.root_nodes).map_err(|mut e| {
307 read_nodes(on_disk, meta.root_nodes).map_err(|mut e| {
307 e.message = format!("{}, when reading root notes", e.message);
308 e.message = format!("{}, when reading root notes", e.message);
308 e
309 e
309 })?,
310 })?,
310 ),
311 ),
311 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
312 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
312 nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(),
313 nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(),
313 ignore_patterns_hash: meta.ignore_patterns_hash,
314 ignore_patterns_hash: meta.ignore_patterns_hash,
314 unreachable_bytes: meta.unreachable_bytes.get(),
315 unreachable_bytes: meta.unreachable_bytes.get(),
315 old_data_size: on_disk.len(),
316 old_data_size: on_disk.len(),
316 old_uuid: Some(uuid),
317 old_uuid: Some(uuid),
318 identity,
317 dirstate_version: DirstateVersion::V2,
319 dirstate_version: DirstateVersion::V2,
318 write_mode: DirstateMapWriteMode::Auto,
320 write_mode: DirstateMapWriteMode::Auto,
319 };
321 };
320 Ok(dirstate_map)
322 Ok(dirstate_map)
321 }
323 }
322
324
323 impl Node {
325 impl Node {
324 pub(super) fn full_path<'on_disk>(
326 pub(super) fn full_path<'on_disk>(
325 &self,
327 &self,
326 on_disk: &'on_disk [u8],
328 on_disk: &'on_disk [u8],
327 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
329 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
328 read_hg_path(on_disk, self.full_path)
330 read_hg_path(on_disk, self.full_path)
329 }
331 }
330
332
331 pub(super) fn base_name_start<'on_disk>(
333 pub(super) fn base_name_start<'on_disk>(
332 &self,
334 &self,
333 ) -> Result<usize, DirstateV2ParseError> {
335 ) -> Result<usize, DirstateV2ParseError> {
334 let start = self.base_name_start.get();
336 let start = self.base_name_start.get();
335 if start < self.full_path.len.get() {
337 if start < self.full_path.len.get() {
336 let start = usize::try_from(start)
338 let start = usize::try_from(start)
337 // u32 -> usize, could only panic on a 16-bit CPU
339 // u32 -> usize, could only panic on a 16-bit CPU
338 .expect("dirstate-v2 base_name_start out of bounds");
340 .expect("dirstate-v2 base_name_start out of bounds");
339 Ok(start)
341 Ok(start)
340 } else {
342 } else {
341 Err(DirstateV2ParseError::new("not enough bytes for base name"))
343 Err(DirstateV2ParseError::new("not enough bytes for base name"))
342 }
344 }
343 }
345 }
344
346
345 pub(super) fn base_name<'on_disk>(
347 pub(super) fn base_name<'on_disk>(
346 &self,
348 &self,
347 on_disk: &'on_disk [u8],
349 on_disk: &'on_disk [u8],
348 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
350 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
349 let full_path = self.full_path(on_disk)?;
351 let full_path = self.full_path(on_disk)?;
350 let base_name_start = self.base_name_start()?;
352 let base_name_start = self.base_name_start()?;
351 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
353 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
352 }
354 }
353
355
354 pub(super) fn path<'on_disk>(
356 pub(super) fn path<'on_disk>(
355 &self,
357 &self,
356 on_disk: &'on_disk [u8],
358 on_disk: &'on_disk [u8],
357 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
359 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
358 Ok(WithBasename::from_raw_parts(
360 Ok(WithBasename::from_raw_parts(
359 Cow::Borrowed(self.full_path(on_disk)?),
361 Cow::Borrowed(self.full_path(on_disk)?),
360 self.base_name_start()?,
362 self.base_name_start()?,
361 ))
363 ))
362 }
364 }
363
365
364 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
366 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
365 self.copy_source.start.get() != 0
367 self.copy_source.start.get() != 0
366 }
368 }
367
369
368 pub(super) fn copy_source<'on_disk>(
370 pub(super) fn copy_source<'on_disk>(
369 &self,
371 &self,
370 on_disk: &'on_disk [u8],
372 on_disk: &'on_disk [u8],
371 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
373 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
372 Ok(if self.has_copy_source() {
374 Ok(if self.has_copy_source() {
373 Some(read_hg_path(on_disk, self.copy_source)?)
375 Some(read_hg_path(on_disk, self.copy_source)?)
374 } else {
376 } else {
375 None
377 None
376 })
378 })
377 }
379 }
378
380
379 fn flags(&self) -> Flags {
381 fn flags(&self) -> Flags {
380 Flags::from_bits_truncate(self.flags.get())
382 Flags::from_bits_truncate(self.flags.get())
381 }
383 }
382
384
383 fn has_entry(&self) -> bool {
385 fn has_entry(&self) -> bool {
384 self.flags().intersects(
386 self.flags().intersects(
385 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
387 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
386 )
388 )
387 }
389 }
388
390
389 pub(super) fn node_data(
391 pub(super) fn node_data(
390 &self,
392 &self,
391 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
393 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
392 if self.has_entry() {
394 if self.has_entry() {
393 Ok(dirstate_map::NodeData::Entry(self.assume_entry()?))
395 Ok(dirstate_map::NodeData::Entry(self.assume_entry()?))
394 } else if let Some(mtime) = self.cached_directory_mtime()? {
396 } else if let Some(mtime) = self.cached_directory_mtime()? {
395 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
397 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
396 } else {
398 } else {
397 Ok(dirstate_map::NodeData::None)
399 Ok(dirstate_map::NodeData::None)
398 }
400 }
399 }
401 }
400
402
401 pub(super) fn cached_directory_mtime(
403 pub(super) fn cached_directory_mtime(
402 &self,
404 &self,
403 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
405 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
404 // For now we do not have code to handle the absence of
406 // For now we do not have code to handle the absence of
405 // ALL_UNKNOWN_RECORDED, so we ignore the mtime if the flag is
407 // ALL_UNKNOWN_RECORDED, so we ignore the mtime if the flag is
406 // unset.
408 // unset.
407 if self.flags().contains(Flags::DIRECTORY)
409 if self.flags().contains(Flags::DIRECTORY)
408 && self.flags().contains(Flags::HAS_MTIME)
410 && self.flags().contains(Flags::HAS_MTIME)
409 && self.flags().contains(Flags::ALL_UNKNOWN_RECORDED)
411 && self.flags().contains(Flags::ALL_UNKNOWN_RECORDED)
410 {
412 {
411 Ok(Some(self.mtime()?))
413 Ok(Some(self.mtime()?))
412 } else {
414 } else {
413 Ok(None)
415 Ok(None)
414 }
416 }
415 }
417 }
416
418
417 fn synthesize_unix_mode(&self) -> u32 {
419 fn synthesize_unix_mode(&self) -> u32 {
418 let file_type = if self.flags().contains(Flags::MODE_IS_SYMLINK) {
420 let file_type = if self.flags().contains(Flags::MODE_IS_SYMLINK) {
419 libc::S_IFLNK
421 libc::S_IFLNK
420 } else {
422 } else {
421 libc::S_IFREG
423 libc::S_IFREG
422 };
424 };
423 let permisions = if self.flags().contains(Flags::MODE_EXEC_PERM) {
425 let permisions = if self.flags().contains(Flags::MODE_EXEC_PERM) {
424 0o755
426 0o755
425 } else {
427 } else {
426 0o644
428 0o644
427 };
429 };
428 (file_type | permisions).into()
430 (file_type | permisions).into()
429 }
431 }
430
432
431 fn mtime(&self) -> Result<TruncatedTimestamp, DirstateV2ParseError> {
433 fn mtime(&self) -> Result<TruncatedTimestamp, DirstateV2ParseError> {
432 let mut m: TruncatedTimestamp = self.mtime.try_into()?;
434 let mut m: TruncatedTimestamp = self.mtime.try_into()?;
433 if self.flags().contains(Flags::MTIME_SECOND_AMBIGUOUS) {
435 if self.flags().contains(Flags::MTIME_SECOND_AMBIGUOUS) {
434 m.second_ambiguous = true;
436 m.second_ambiguous = true;
435 }
437 }
436 Ok(m)
438 Ok(m)
437 }
439 }
438
440
439 fn assume_entry(&self) -> Result<DirstateEntry, DirstateV2ParseError> {
441 fn assume_entry(&self) -> Result<DirstateEntry, DirstateV2ParseError> {
440 // TODO: convert through raw bits instead?
442 // TODO: convert through raw bits instead?
441 let wc_tracked = self.flags().contains(Flags::WDIR_TRACKED);
443 let wc_tracked = self.flags().contains(Flags::WDIR_TRACKED);
442 let p1_tracked = self.flags().contains(Flags::P1_TRACKED);
444 let p1_tracked = self.flags().contains(Flags::P1_TRACKED);
443 let p2_info = self.flags().contains(Flags::P2_INFO);
445 let p2_info = self.flags().contains(Flags::P2_INFO);
444 let mode_size = if self.flags().contains(Flags::HAS_MODE_AND_SIZE)
446 let mode_size = if self.flags().contains(Flags::HAS_MODE_AND_SIZE)
445 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
447 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
446 {
448 {
447 Some((self.synthesize_unix_mode(), self.size.into()))
449 Some((self.synthesize_unix_mode(), self.size.into()))
448 } else {
450 } else {
449 None
451 None
450 };
452 };
451 let mtime = if self.flags().contains(Flags::HAS_MTIME)
453 let mtime = if self.flags().contains(Flags::HAS_MTIME)
452 && !self.flags().contains(Flags::DIRECTORY)
454 && !self.flags().contains(Flags::DIRECTORY)
453 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
455 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
454 {
456 {
455 Some(self.mtime()?)
457 Some(self.mtime()?)
456 } else {
458 } else {
457 None
459 None
458 };
460 };
459 let fallback_exec = if self.flags().contains(Flags::HAS_FALLBACK_EXEC)
461 let fallback_exec = if self.flags().contains(Flags::HAS_FALLBACK_EXEC)
460 {
462 {
461 Some(self.flags().contains(Flags::FALLBACK_EXEC))
463 Some(self.flags().contains(Flags::FALLBACK_EXEC))
462 } else {
464 } else {
463 None
465 None
464 };
466 };
465 let fallback_symlink =
467 let fallback_symlink =
466 if self.flags().contains(Flags::HAS_FALLBACK_SYMLINK) {
468 if self.flags().contains(Flags::HAS_FALLBACK_SYMLINK) {
467 Some(self.flags().contains(Flags::FALLBACK_SYMLINK))
469 Some(self.flags().contains(Flags::FALLBACK_SYMLINK))
468 } else {
470 } else {
469 None
471 None
470 };
472 };
471 Ok(DirstateEntry::from_v2_data(DirstateV2Data {
473 Ok(DirstateEntry::from_v2_data(DirstateV2Data {
472 wc_tracked,
474 wc_tracked,
473 p1_tracked,
475 p1_tracked,
474 p2_info,
476 p2_info,
475 mode_size,
477 mode_size,
476 mtime,
478 mtime,
477 fallback_exec,
479 fallback_exec,
478 fallback_symlink,
480 fallback_symlink,
479 }))
481 }))
480 }
482 }
481
483
482 pub(super) fn entry(
484 pub(super) fn entry(
483 &self,
485 &self,
484 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
486 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
485 if self.has_entry() {
487 if self.has_entry() {
486 Ok(Some(self.assume_entry()?))
488 Ok(Some(self.assume_entry()?))
487 } else {
489 } else {
488 Ok(None)
490 Ok(None)
489 }
491 }
490 }
492 }
491
493
492 pub(super) fn children<'on_disk>(
494 pub(super) fn children<'on_disk>(
493 &self,
495 &self,
494 on_disk: &'on_disk [u8],
496 on_disk: &'on_disk [u8],
495 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
497 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
496 read_nodes(on_disk, self.children)
498 read_nodes(on_disk, self.children)
497 }
499 }
498
500
499 pub(super) fn to_in_memory_node<'on_disk>(
501 pub(super) fn to_in_memory_node<'on_disk>(
500 &self,
502 &self,
501 on_disk: &'on_disk [u8],
503 on_disk: &'on_disk [u8],
502 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
504 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
503 Ok(dirstate_map::Node {
505 Ok(dirstate_map::Node {
504 children: dirstate_map::ChildNodes::OnDisk(
506 children: dirstate_map::ChildNodes::OnDisk(
505 self.children(on_disk)?,
507 self.children(on_disk)?,
506 ),
508 ),
507 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
509 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
508 data: self.node_data()?,
510 data: self.node_data()?,
509 descendants_with_entry_count: self
511 descendants_with_entry_count: self
510 .descendants_with_entry_count
512 .descendants_with_entry_count
511 .get(),
513 .get(),
512 tracked_descendants_count: self.tracked_descendants_count.get(),
514 tracked_descendants_count: self.tracked_descendants_count.get(),
513 })
515 })
514 }
516 }
515
517
516 fn from_dirstate_entry(
518 fn from_dirstate_entry(
517 entry: &DirstateEntry,
519 entry: &DirstateEntry,
518 ) -> (Flags, U32Be, PackedTruncatedTimestamp) {
520 ) -> (Flags, U32Be, PackedTruncatedTimestamp) {
519 let DirstateV2Data {
521 let DirstateV2Data {
520 wc_tracked,
522 wc_tracked,
521 p1_tracked,
523 p1_tracked,
522 p2_info,
524 p2_info,
523 mode_size: mode_size_opt,
525 mode_size: mode_size_opt,
524 mtime: mtime_opt,
526 mtime: mtime_opt,
525 fallback_exec,
527 fallback_exec,
526 fallback_symlink,
528 fallback_symlink,
527 } = entry.v2_data();
529 } = entry.v2_data();
528 // TODO: convert through raw flag bits instead?
530 // TODO: convert through raw flag bits instead?
529 let mut flags = Flags::empty();
531 let mut flags = Flags::empty();
530 flags.set(Flags::WDIR_TRACKED, wc_tracked);
532 flags.set(Flags::WDIR_TRACKED, wc_tracked);
531 flags.set(Flags::P1_TRACKED, p1_tracked);
533 flags.set(Flags::P1_TRACKED, p1_tracked);
532 flags.set(Flags::P2_INFO, p2_info);
534 flags.set(Flags::P2_INFO, p2_info);
533 let size = if let Some((m, s)) = mode_size_opt {
535 let size = if let Some((m, s)) = mode_size_opt {
534 let exec_perm = m & (libc::S_IXUSR as u32) != 0;
536 let exec_perm = m & (libc::S_IXUSR as u32) != 0;
535 let is_symlink = m & (libc::S_IFMT as u32) == libc::S_IFLNK as u32;
537 let is_symlink = m & (libc::S_IFMT as u32) == libc::S_IFLNK as u32;
536 flags.set(Flags::MODE_EXEC_PERM, exec_perm);
538 flags.set(Flags::MODE_EXEC_PERM, exec_perm);
537 flags.set(Flags::MODE_IS_SYMLINK, is_symlink);
539 flags.set(Flags::MODE_IS_SYMLINK, is_symlink);
538 flags.insert(Flags::HAS_MODE_AND_SIZE);
540 flags.insert(Flags::HAS_MODE_AND_SIZE);
539 s.into()
541 s.into()
540 } else {
542 } else {
541 0.into()
543 0.into()
542 };
544 };
543 let mtime = if let Some(m) = mtime_opt {
545 let mtime = if let Some(m) = mtime_opt {
544 flags.insert(Flags::HAS_MTIME);
546 flags.insert(Flags::HAS_MTIME);
545 if m.second_ambiguous {
547 if m.second_ambiguous {
546 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS);
548 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS);
547 };
549 };
548 m.into()
550 m.into()
549 } else {
551 } else {
550 PackedTruncatedTimestamp::null()
552 PackedTruncatedTimestamp::null()
551 };
553 };
552 if let Some(f_exec) = fallback_exec {
554 if let Some(f_exec) = fallback_exec {
553 flags.insert(Flags::HAS_FALLBACK_EXEC);
555 flags.insert(Flags::HAS_FALLBACK_EXEC);
554 if f_exec {
556 if f_exec {
555 flags.insert(Flags::FALLBACK_EXEC);
557 flags.insert(Flags::FALLBACK_EXEC);
556 }
558 }
557 }
559 }
558 if let Some(f_symlink) = fallback_symlink {
560 if let Some(f_symlink) = fallback_symlink {
559 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
561 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
560 if f_symlink {
562 if f_symlink {
561 flags.insert(Flags::FALLBACK_SYMLINK);
563 flags.insert(Flags::FALLBACK_SYMLINK);
562 }
564 }
563 }
565 }
564 (flags, size, mtime)
566 (flags, size, mtime)
565 }
567 }
566 }
568 }
567
569
568 fn read_hg_path(
570 fn read_hg_path(
569 on_disk: &[u8],
571 on_disk: &[u8],
570 slice: PathSlice,
572 slice: PathSlice,
571 ) -> Result<&HgPath, DirstateV2ParseError> {
573 ) -> Result<&HgPath, DirstateV2ParseError> {
572 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
574 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
573 }
575 }
574
576
575 fn read_nodes(
577 fn read_nodes(
576 on_disk: &[u8],
578 on_disk: &[u8],
577 slice: ChildNodes,
579 slice: ChildNodes,
578 ) -> Result<&[Node], DirstateV2ParseError> {
580 ) -> Result<&[Node], DirstateV2ParseError> {
579 read_slice(on_disk, slice.start, slice.len.get())
581 read_slice(on_disk, slice.start, slice.len.get())
580 }
582 }
581
583
582 fn read_slice<T, Len>(
584 fn read_slice<T, Len>(
583 on_disk: &[u8],
585 on_disk: &[u8],
584 start: Offset,
586 start: Offset,
585 len: Len,
587 len: Len,
586 ) -> Result<&[T], DirstateV2ParseError>
588 ) -> Result<&[T], DirstateV2ParseError>
587 where
589 where
588 T: BytesCast,
590 T: BytesCast,
589 Len: TryInto<usize>,
591 Len: TryInto<usize>,
590 {
592 {
591 // Either `usize::MAX` would result in "out of bounds" error since a single
593 // Either `usize::MAX` would result in "out of bounds" error since a single
592 // `&[u8]` cannot occupy the entire addess space.
594 // `&[u8]` cannot occupy the entire addess space.
593 let start = start.get().try_into().unwrap_or(std::usize::MAX);
595 let start = start.get().try_into().unwrap_or(std::usize::MAX);
594 let len = len.try_into().unwrap_or(std::usize::MAX);
596 let len = len.try_into().unwrap_or(std::usize::MAX);
595 let bytes = match on_disk.get(start..) {
597 let bytes = match on_disk.get(start..) {
596 Some(bytes) => bytes,
598 Some(bytes) => bytes,
597 None => {
599 None => {
598 return Err(DirstateV2ParseError::new(
600 return Err(DirstateV2ParseError::new(
599 "not enough bytes from disk",
601 "not enough bytes from disk",
600 ))
602 ))
601 }
603 }
602 };
604 };
603 T::slice_from_bytes(bytes, len)
605 T::slice_from_bytes(bytes, len)
604 .map_err(|e| {
606 .map_err(|e| {
605 DirstateV2ParseError::new(format!("when reading a slice, {}", e))
607 DirstateV2ParseError::new(format!("when reading a slice, {}", e))
606 })
608 })
607 .map(|(slice, _rest)| slice)
609 .map(|(slice, _rest)| slice)
608 }
610 }
609
611
610 pub(crate) fn for_each_tracked_path<'on_disk>(
612 pub(crate) fn for_each_tracked_path<'on_disk>(
611 on_disk: &'on_disk [u8],
613 on_disk: &'on_disk [u8],
612 metadata: &[u8],
614 metadata: &[u8],
613 mut f: impl FnMut(&'on_disk HgPath),
615 mut f: impl FnMut(&'on_disk HgPath),
614 ) -> Result<(), DirstateV2ParseError> {
616 ) -> Result<(), DirstateV2ParseError> {
615 let (meta, _) = TreeMetadata::from_bytes(metadata).map_err(|e| {
617 let (meta, _) = TreeMetadata::from_bytes(metadata).map_err(|e| {
616 DirstateV2ParseError::new(format!("when parsing tree metadata, {}", e))
618 DirstateV2ParseError::new(format!("when parsing tree metadata, {}", e))
617 })?;
619 })?;
618 fn recur<'on_disk>(
620 fn recur<'on_disk>(
619 on_disk: &'on_disk [u8],
621 on_disk: &'on_disk [u8],
620 nodes: ChildNodes,
622 nodes: ChildNodes,
621 f: &mut impl FnMut(&'on_disk HgPath),
623 f: &mut impl FnMut(&'on_disk HgPath),
622 ) -> Result<(), DirstateV2ParseError> {
624 ) -> Result<(), DirstateV2ParseError> {
623 for node in read_nodes(on_disk, nodes)? {
625 for node in read_nodes(on_disk, nodes)? {
624 if let Some(entry) = node.entry()? {
626 if let Some(entry) = node.entry()? {
625 if entry.tracked() {
627 if entry.tracked() {
626 f(node.full_path(on_disk)?)
628 f(node.full_path(on_disk)?)
627 }
629 }
628 }
630 }
629 recur(on_disk, node.children, f)?
631 recur(on_disk, node.children, f)?
630 }
632 }
631 Ok(())
633 Ok(())
632 }
634 }
633 recur(on_disk, meta.root_nodes, &mut f)
635 recur(on_disk, meta.root_nodes, &mut f)
634 }
636 }
635
637
636 /// Returns new data and metadata, together with whether that data should be
638 /// Returns new data and metadata, together with whether that data should be
637 /// appended to the existing data file whose content is at
639 /// appended to the existing data file whose content is at
638 /// `dirstate_map.on_disk` (true), instead of written to a new data file
640 /// `dirstate_map.on_disk` (true), instead of written to a new data file
639 /// (false), and the previous size of data on disk.
641 /// (false), and the previous size of data on disk.
640 pub(super) fn write(
642 pub(super) fn write(
641 dirstate_map: &DirstateMap,
643 dirstate_map: &DirstateMap,
642 write_mode: DirstateMapWriteMode,
644 write_mode: DirstateMapWriteMode,
643 ) -> Result<(Vec<u8>, TreeMetadata, bool, usize), DirstateError> {
645 ) -> Result<(Vec<u8>, TreeMetadata, bool, usize), DirstateError> {
644 let append = match write_mode {
646 let append = match write_mode {
645 DirstateMapWriteMode::Auto => dirstate_map.write_should_append(),
647 DirstateMapWriteMode::Auto => dirstate_map.write_should_append(),
646 DirstateMapWriteMode::ForceNewDataFile => false,
648 DirstateMapWriteMode::ForceNewDataFile => false,
647 DirstateMapWriteMode::ForceAppend => true,
649 DirstateMapWriteMode::ForceAppend => true,
648 };
650 };
649 if append {
651 if append {
650 log::trace!("appending to the dirstate data file");
652 log::trace!("appending to the dirstate data file");
651 } else {
653 } else {
652 log::trace!("creating new dirstate data file");
654 log::trace!("creating new dirstate data file");
653 }
655 }
654
656
655 // This ignores the space for paths, and for nodes without an entry.
657 // This ignores the space for paths, and for nodes without an entry.
656 // TODO: better estimate? Skip the `Vec` and write to a file directly?
658 // TODO: better estimate? Skip the `Vec` and write to a file directly?
657 let size_guess = std::mem::size_of::<Node>()
659 let size_guess = std::mem::size_of::<Node>()
658 * dirstate_map.nodes_with_entry_count as usize;
660 * dirstate_map.nodes_with_entry_count as usize;
659
661
660 let mut writer = Writer {
662 let mut writer = Writer {
661 dirstate_map,
663 dirstate_map,
662 append,
664 append,
663 out: Vec::with_capacity(size_guess),
665 out: Vec::with_capacity(size_guess),
664 };
666 };
665
667
666 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
668 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
667
669
668 let unreachable_bytes = if append {
670 let unreachable_bytes = if append {
669 dirstate_map.unreachable_bytes
671 dirstate_map.unreachable_bytes
670 } else {
672 } else {
671 0
673 0
672 };
674 };
673 let meta = TreeMetadata {
675 let meta = TreeMetadata {
674 root_nodes,
676 root_nodes,
675 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
677 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
676 nodes_with_copy_source_count: dirstate_map
678 nodes_with_copy_source_count: dirstate_map
677 .nodes_with_copy_source_count
679 .nodes_with_copy_source_count
678 .into(),
680 .into(),
679 unreachable_bytes: unreachable_bytes.into(),
681 unreachable_bytes: unreachable_bytes.into(),
680 unused: [0; 4],
682 unused: [0; 4],
681 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
683 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
682 };
684 };
683 Ok((writer.out, meta, append, dirstate_map.old_data_size))
685 Ok((writer.out, meta, append, dirstate_map.old_data_size))
684 }
686 }
685
687
686 struct Writer<'dmap, 'on_disk> {
688 struct Writer<'dmap, 'on_disk> {
687 dirstate_map: &'dmap DirstateMap<'on_disk>,
689 dirstate_map: &'dmap DirstateMap<'on_disk>,
688 append: bool,
690 append: bool,
689 out: Vec<u8>,
691 out: Vec<u8>,
690 }
692 }
691
693
692 impl Writer<'_, '_> {
694 impl Writer<'_, '_> {
693 fn write_nodes(
695 fn write_nodes(
694 &mut self,
696 &mut self,
695 nodes: dirstate_map::ChildNodesRef,
697 nodes: dirstate_map::ChildNodesRef,
696 ) -> Result<ChildNodes, DirstateError> {
698 ) -> Result<ChildNodes, DirstateError> {
697 // Reuse already-written nodes if possible
699 // Reuse already-written nodes if possible
698 if self.append {
700 if self.append {
699 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
701 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
700 let start = self.on_disk_offset_of(nodes_slice).expect(
702 let start = self.on_disk_offset_of(nodes_slice).expect(
701 "dirstate-v2 OnDisk nodes not found within on_disk",
703 "dirstate-v2 OnDisk nodes not found within on_disk",
702 );
704 );
703 let len = child_nodes_len_from_usize(nodes_slice.len());
705 let len = child_nodes_len_from_usize(nodes_slice.len());
704 return Ok(ChildNodes { start, len });
706 return Ok(ChildNodes { start, len });
705 }
707 }
706 }
708 }
707
709
708 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
710 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
709 // undefined iteration order. Sort to enable binary search in the
711 // undefined iteration order. Sort to enable binary search in the
710 // written file.
712 // written file.
711 let nodes = nodes.sorted();
713 let nodes = nodes.sorted();
712 let nodes_len = nodes.len();
714 let nodes_len = nodes.len();
713
715
714 // First accumulate serialized nodes in a `Vec`
716 // First accumulate serialized nodes in a `Vec`
715 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
717 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
716 for node in nodes {
718 for node in nodes {
717 let children =
719 let children =
718 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
720 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
719 let full_path = node.full_path(self.dirstate_map.on_disk)?;
721 let full_path = node.full_path(self.dirstate_map.on_disk)?;
720 let full_path = self.write_path(full_path.as_bytes());
722 let full_path = self.write_path(full_path.as_bytes());
721 let copy_source = if let Some(source) =
723 let copy_source = if let Some(source) =
722 node.copy_source(self.dirstate_map.on_disk)?
724 node.copy_source(self.dirstate_map.on_disk)?
723 {
725 {
724 self.write_path(source.as_bytes())
726 self.write_path(source.as_bytes())
725 } else {
727 } else {
726 PathSlice {
728 PathSlice {
727 start: 0.into(),
729 start: 0.into(),
728 len: 0.into(),
730 len: 0.into(),
729 }
731 }
730 };
732 };
731 on_disk_nodes.push(match node {
733 on_disk_nodes.push(match node {
732 NodeRef::InMemory(path, node) => {
734 NodeRef::InMemory(path, node) => {
733 let (flags, size, mtime) = match &node.data {
735 let (flags, size, mtime) = match &node.data {
734 dirstate_map::NodeData::Entry(entry) => {
736 dirstate_map::NodeData::Entry(entry) => {
735 Node::from_dirstate_entry(entry)
737 Node::from_dirstate_entry(entry)
736 }
738 }
737 dirstate_map::NodeData::CachedDirectory { mtime } => {
739 dirstate_map::NodeData::CachedDirectory { mtime } => {
738 // we currently never set a mtime if unknown file
740 // we currently never set a mtime if unknown file
739 // are present.
741 // are present.
740 // So if we have a mtime for a directory, we know
742 // So if we have a mtime for a directory, we know
741 // they are no unknown
743 // they are no unknown
742 // files and we
744 // files and we
743 // blindly set ALL_UNKNOWN_RECORDED.
745 // blindly set ALL_UNKNOWN_RECORDED.
744 //
746 //
745 // We never set ALL_IGNORED_RECORDED since we
747 // We never set ALL_IGNORED_RECORDED since we
746 // don't track that case
748 // don't track that case
747 // currently.
749 // currently.
748 let mut flags = Flags::DIRECTORY
750 let mut flags = Flags::DIRECTORY
749 | Flags::HAS_MTIME
751 | Flags::HAS_MTIME
750 | Flags::ALL_UNKNOWN_RECORDED;
752 | Flags::ALL_UNKNOWN_RECORDED;
751 if mtime.second_ambiguous {
753 if mtime.second_ambiguous {
752 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS)
754 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS)
753 }
755 }
754 (flags, 0.into(), (*mtime).into())
756 (flags, 0.into(), (*mtime).into())
755 }
757 }
756 dirstate_map::NodeData::None => (
758 dirstate_map::NodeData::None => (
757 Flags::DIRECTORY,
759 Flags::DIRECTORY,
758 0.into(),
760 0.into(),
759 PackedTruncatedTimestamp::null(),
761 PackedTruncatedTimestamp::null(),
760 ),
762 ),
761 };
763 };
762 Node {
764 Node {
763 children,
765 children,
764 copy_source,
766 copy_source,
765 full_path,
767 full_path,
766 base_name_start: u16::try_from(path.base_name_start())
768 base_name_start: u16::try_from(path.base_name_start())
767 // Could only panic for paths over 64 KiB
769 // Could only panic for paths over 64 KiB
768 .expect("dirstate-v2 path length overflow")
770 .expect("dirstate-v2 path length overflow")
769 .into(),
771 .into(),
770 descendants_with_entry_count: node
772 descendants_with_entry_count: node
771 .descendants_with_entry_count
773 .descendants_with_entry_count
772 .into(),
774 .into(),
773 tracked_descendants_count: node
775 tracked_descendants_count: node
774 .tracked_descendants_count
776 .tracked_descendants_count
775 .into(),
777 .into(),
776 flags: flags.bits().into(),
778 flags: flags.bits().into(),
777 size,
779 size,
778 mtime,
780 mtime,
779 }
781 }
780 }
782 }
781 NodeRef::OnDisk(node) => Node {
783 NodeRef::OnDisk(node) => Node {
782 children,
784 children,
783 copy_source,
785 copy_source,
784 full_path,
786 full_path,
785 ..*node
787 ..*node
786 },
788 },
787 })
789 })
788 }
790 }
789 // … so we can write them contiguously, after writing everything else
791 // … so we can write them contiguously, after writing everything else
790 // they refer to.
792 // they refer to.
791 let start = self.current_offset();
793 let start = self.current_offset();
792 let len = child_nodes_len_from_usize(nodes_len);
794 let len = child_nodes_len_from_usize(nodes_len);
793 self.out.extend(on_disk_nodes.as_bytes());
795 self.out.extend(on_disk_nodes.as_bytes());
794 Ok(ChildNodes { start, len })
796 Ok(ChildNodes { start, len })
795 }
797 }
796
798
797 /// If the given slice of items is within `on_disk`, returns its offset
799 /// If the given slice of items is within `on_disk`, returns its offset
798 /// from the start of `on_disk`.
800 /// from the start of `on_disk`.
799 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
801 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
800 where
802 where
801 T: BytesCast,
803 T: BytesCast,
802 {
804 {
803 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
805 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
804 let start = slice.as_ptr() as usize;
806 let start = slice.as_ptr() as usize;
805 let end = start + slice.len();
807 let end = start + slice.len();
806 start..=end
808 start..=end
807 }
809 }
808 let slice_addresses = address_range(slice.as_bytes());
810 let slice_addresses = address_range(slice.as_bytes());
809 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
811 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
810 if on_disk_addresses.contains(slice_addresses.start())
812 if on_disk_addresses.contains(slice_addresses.start())
811 && on_disk_addresses.contains(slice_addresses.end())
813 && on_disk_addresses.contains(slice_addresses.end())
812 {
814 {
813 let offset = slice_addresses.start() - on_disk_addresses.start();
815 let offset = slice_addresses.start() - on_disk_addresses.start();
814 Some(offset_from_usize(offset))
816 Some(offset_from_usize(offset))
815 } else {
817 } else {
816 None
818 None
817 }
819 }
818 }
820 }
819
821
820 fn current_offset(&mut self) -> Offset {
822 fn current_offset(&mut self) -> Offset {
821 let mut offset = self.out.len();
823 let mut offset = self.out.len();
822 if self.append {
824 if self.append {
823 offset += self.dirstate_map.on_disk.len()
825 offset += self.dirstate_map.on_disk.len()
824 }
826 }
825 offset_from_usize(offset)
827 offset_from_usize(offset)
826 }
828 }
827
829
828 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
830 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
829 let len = path_len_from_usize(slice.len());
831 let len = path_len_from_usize(slice.len());
830 // Reuse an already-written path if possible
832 // Reuse an already-written path if possible
831 if self.append {
833 if self.append {
832 if let Some(start) = self.on_disk_offset_of(slice) {
834 if let Some(start) = self.on_disk_offset_of(slice) {
833 return PathSlice { start, len };
835 return PathSlice { start, len };
834 }
836 }
835 }
837 }
836 let start = self.current_offset();
838 let start = self.current_offset();
837 self.out.extend(slice.as_bytes());
839 self.out.extend(slice.as_bytes());
838 PathSlice { start, len }
840 PathSlice { start, len }
839 }
841 }
840 }
842 }
841
843
842 fn offset_from_usize(x: usize) -> Offset {
844 fn offset_from_usize(x: usize) -> Offset {
843 u32::try_from(x)
845 u32::try_from(x)
844 // Could only panic for a dirstate file larger than 4 GiB
846 // Could only panic for a dirstate file larger than 4 GiB
845 .expect("dirstate-v2 offset overflow")
847 .expect("dirstate-v2 offset overflow")
846 .into()
848 .into()
847 }
849 }
848
850
849 fn child_nodes_len_from_usize(x: usize) -> Size {
851 fn child_nodes_len_from_usize(x: usize) -> Size {
850 u32::try_from(x)
852 u32::try_from(x)
851 // Could only panic with over 4 billion nodes
853 // Could only panic with over 4 billion nodes
852 .expect("dirstate-v2 slice length overflow")
854 .expect("dirstate-v2 slice length overflow")
853 .into()
855 .into()
854 }
856 }
855
857
856 fn path_len_from_usize(x: usize) -> PathSize {
858 fn path_len_from_usize(x: usize) -> PathSize {
857 u16::try_from(x)
859 u16::try_from(x)
858 // Could only panic for paths over 64 KiB
860 // Could only panic for paths over 64 KiB
859 .expect("dirstate-v2 path length overflow")
861 .expect("dirstate-v2 path length overflow")
860 .into()
862 .into()
861 }
863 }
862
864
863 impl From<TruncatedTimestamp> for PackedTruncatedTimestamp {
865 impl From<TruncatedTimestamp> for PackedTruncatedTimestamp {
864 fn from(timestamp: TruncatedTimestamp) -> Self {
866 fn from(timestamp: TruncatedTimestamp) -> Self {
865 Self {
867 Self {
866 truncated_seconds: timestamp.truncated_seconds().into(),
868 truncated_seconds: timestamp.truncated_seconds().into(),
867 nanoseconds: timestamp.nanoseconds().into(),
869 nanoseconds: timestamp.nanoseconds().into(),
868 }
870 }
869 }
871 }
870 }
872 }
871
873
872 impl TryFrom<PackedTruncatedTimestamp> for TruncatedTimestamp {
874 impl TryFrom<PackedTruncatedTimestamp> for TruncatedTimestamp {
873 type Error = DirstateV2ParseError;
875 type Error = DirstateV2ParseError;
874
876
875 fn try_from(
877 fn try_from(
876 timestamp: PackedTruncatedTimestamp,
878 timestamp: PackedTruncatedTimestamp,
877 ) -> Result<Self, Self::Error> {
879 ) -> Result<Self, Self::Error> {
878 Self::from_already_truncated(
880 Self::from_already_truncated(
879 timestamp.truncated_seconds.get(),
881 timestamp.truncated_seconds.get(),
880 timestamp.nanoseconds.get(),
882 timestamp.nanoseconds.get(),
881 false,
883 false,
882 )
884 )
883 }
885 }
884 }
886 }
885 impl PackedTruncatedTimestamp {
887 impl PackedTruncatedTimestamp {
886 fn null() -> Self {
888 fn null() -> Self {
887 Self {
889 Self {
888 truncated_seconds: 0.into(),
890 truncated_seconds: 0.into(),
889 nanoseconds: 0.into(),
891 nanoseconds: 0.into(),
890 }
892 }
891 }
893 }
892 }
894 }
@@ -1,98 +1,106 b''
1 use crate::{DirstateError, DirstateParents};
1 use crate::{DirstateError, DirstateParents};
2
2
3 use super::dirstate_map::DirstateMap;
3 use super::dirstate_map::DirstateMap;
4 use std::ops::Deref;
4 use std::ops::Deref;
5
5
6 use ouroboros::self_referencing;
6 use ouroboros::self_referencing;
7
7
8 /// Keep a `DirstateMap<'on_disk>` next to the `on_disk` buffer that it
8 /// Keep a `DirstateMap<'on_disk>` next to the `on_disk` buffer that it
9 /// borrows.
9 /// borrows.
10 #[self_referencing]
10 #[self_referencing]
11 pub struct OwningDirstateMap {
11 pub struct OwningDirstateMap {
12 on_disk: Box<dyn Deref<Target = [u8]> + Send>,
12 on_disk: Box<dyn Deref<Target = [u8]> + Send>,
13 #[borrows(on_disk)]
13 #[borrows(on_disk)]
14 #[covariant]
14 #[covariant]
15 map: DirstateMap<'this>,
15 map: DirstateMap<'this>,
16 }
16 }
17
17
18 impl OwningDirstateMap {
18 impl OwningDirstateMap {
19 pub fn new_empty<OnDisk>(on_disk: OnDisk) -> Self
19 pub fn new_empty<OnDisk>(on_disk: OnDisk) -> Self
20 where
20 where
21 OnDisk: Deref<Target = [u8]> + Send + 'static,
21 OnDisk: Deref<Target = [u8]> + Send + 'static,
22 {
22 {
23 let on_disk = Box::new(on_disk);
23 let on_disk = Box::new(on_disk);
24
24
25 OwningDirstateMapBuilder {
25 OwningDirstateMapBuilder {
26 on_disk,
26 on_disk,
27 map_builder: |bytes| DirstateMap::empty(&bytes),
27 map_builder: |bytes| DirstateMap::empty(&bytes),
28 }
28 }
29 .build()
29 .build()
30 }
30 }
31
31
32 pub fn new_v1<OnDisk>(
32 pub fn new_v1<OnDisk>(
33 on_disk: OnDisk,
33 on_disk: OnDisk,
34 identity: Option<u64>,
34 ) -> Result<(Self, DirstateParents), DirstateError>
35 ) -> Result<(Self, DirstateParents), DirstateError>
35 where
36 where
36 OnDisk: Deref<Target = [u8]> + Send + 'static,
37 OnDisk: Deref<Target = [u8]> + Send + 'static,
37 {
38 {
38 let on_disk = Box::new(on_disk);
39 let on_disk = Box::new(on_disk);
39 let mut parents = DirstateParents::NULL;
40 let mut parents = DirstateParents::NULL;
40
41
41 Ok((
42 Ok((
42 OwningDirstateMapTryBuilder {
43 OwningDirstateMapTryBuilder {
43 on_disk,
44 on_disk,
44 map_builder: |bytes| {
45 map_builder: |bytes| {
45 DirstateMap::new_v1(&bytes).map(|(dmap, p)| {
46 DirstateMap::new_v1(&bytes, identity).map(|(dmap, p)| {
46 parents = p.unwrap_or(DirstateParents::NULL);
47 parents = p.unwrap_or(DirstateParents::NULL);
47 dmap
48 dmap
48 })
49 })
49 },
50 },
50 }
51 }
51 .try_build()?,
52 .try_build()?,
52 parents,
53 parents,
53 ))
54 ))
54 }
55 }
55
56
56 pub fn new_v2<OnDisk>(
57 pub fn new_v2<OnDisk>(
57 on_disk: OnDisk,
58 on_disk: OnDisk,
58 data_size: usize,
59 data_size: usize,
59 metadata: &[u8],
60 metadata: &[u8],
60 uuid: Vec<u8>,
61 uuid: Vec<u8>,
62 identity: Option<u64>,
61 ) -> Result<Self, DirstateError>
63 ) -> Result<Self, DirstateError>
62 where
64 where
63 OnDisk: Deref<Target = [u8]> + Send + 'static,
65 OnDisk: Deref<Target = [u8]> + Send + 'static,
64 {
66 {
65 let on_disk = Box::new(on_disk);
67 let on_disk = Box::new(on_disk);
66
68
67 OwningDirstateMapTryBuilder {
69 OwningDirstateMapTryBuilder {
68 on_disk,
70 on_disk,
69 map_builder: |bytes| {
71 map_builder: |bytes| {
70 DirstateMap::new_v2(&bytes, data_size, metadata, uuid)
72 DirstateMap::new_v2(
73 &bytes, data_size, metadata, uuid, identity,
74 )
71 },
75 },
72 }
76 }
73 .try_build()
77 .try_build()
74 }
78 }
75
79
76 pub fn with_dmap_mut<R>(
80 pub fn with_dmap_mut<R>(
77 &mut self,
81 &mut self,
78 f: impl FnOnce(&mut DirstateMap) -> R,
82 f: impl FnOnce(&mut DirstateMap) -> R,
79 ) -> R {
83 ) -> R {
80 self.with_map_mut(f)
84 self.with_map_mut(f)
81 }
85 }
82
86
83 pub fn get_map(&self) -> &DirstateMap {
87 pub fn get_map(&self) -> &DirstateMap {
84 self.borrow_map()
88 self.borrow_map()
85 }
89 }
86
90
87 pub fn on_disk(&self) -> &[u8] {
91 pub fn on_disk(&self) -> &[u8] {
88 self.borrow_on_disk()
92 self.borrow_on_disk()
89 }
93 }
90
94
91 pub fn old_uuid(&self) -> Option<&[u8]> {
95 pub fn old_uuid(&self) -> Option<&[u8]> {
92 self.get_map().old_uuid.as_deref()
96 self.get_map().old_uuid.as_deref()
93 }
97 }
94
98
99 pub fn old_identity(&self) -> Option<u64> {
100 self.get_map().identity
101 }
102
95 pub fn old_data_size(&self) -> usize {
103 pub fn old_data_size(&self) -> usize {
96 self.get_map().old_data_size
104 self.get_map().old_data_size
97 }
105 }
98 }
106 }
@@ -1,700 +1,738 b''
1 use crate::changelog::Changelog;
1 use crate::changelog::Changelog;
2 use crate::config::{Config, ConfigError, ConfigParseError};
2 use crate::config::{Config, ConfigError, ConfigParseError};
3 use crate::dirstate::DirstateParents;
3 use crate::dirstate::DirstateParents;
4 use crate::dirstate_tree::dirstate_map::DirstateMapWriteMode;
4 use crate::dirstate_tree::dirstate_map::DirstateMapWriteMode;
5 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
5 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
6 use crate::dirstate_tree::owning::OwningDirstateMap;
6 use crate::dirstate_tree::owning::OwningDirstateMap;
7 use crate::errors::HgResultExt;
7 use crate::errors::HgResultExt;
8 use crate::errors::{HgError, IoResultExt};
8 use crate::errors::{HgError, IoResultExt};
9 use crate::lock::{try_with_lock_no_wait, LockError};
9 use crate::lock::{try_with_lock_no_wait, LockError};
10 use crate::manifest::{Manifest, Manifestlog};
10 use crate::manifest::{Manifest, Manifestlog};
11 use crate::revlog::filelog::Filelog;
11 use crate::revlog::filelog::Filelog;
12 use crate::revlog::revlog::RevlogError;
12 use crate::revlog::revlog::RevlogError;
13 use crate::utils::debug::debug_wait_for_file_or_print;
13 use crate::utils::debug::debug_wait_for_file_or_print;
14 use crate::utils::files::get_path_from_bytes;
14 use crate::utils::files::get_path_from_bytes;
15 use crate::utils::hg_path::HgPath;
15 use crate::utils::hg_path::HgPath;
16 use crate::utils::SliceExt;
16 use crate::utils::SliceExt;
17 use crate::vfs::{is_dir, is_file, Vfs};
17 use crate::vfs::{is_dir, is_file, Vfs};
18 use crate::{requirements, NodePrefix};
18 use crate::{requirements, NodePrefix};
19 use crate::{DirstateError, Revision};
19 use crate::{DirstateError, Revision};
20 use std::cell::{Ref, RefCell, RefMut};
20 use std::cell::{Ref, RefCell, RefMut};
21 use std::collections::HashSet;
21 use std::collections::HashSet;
22 use std::io::Seek;
22 use std::io::Seek;
23 use std::io::SeekFrom;
23 use std::io::SeekFrom;
24 use std::io::Write as IoWrite;
24 use std::io::Write as IoWrite;
25 use std::path::{Path, PathBuf};
25 use std::path::{Path, PathBuf};
26
26
27 const V2_MAX_READ_ATTEMPTS: usize = 5;
27 const V2_MAX_READ_ATTEMPTS: usize = 5;
28
28
29 /// A repository on disk
29 /// A repository on disk
30 pub struct Repo {
30 pub struct Repo {
31 working_directory: PathBuf,
31 working_directory: PathBuf,
32 dot_hg: PathBuf,
32 dot_hg: PathBuf,
33 store: PathBuf,
33 store: PathBuf,
34 requirements: HashSet<String>,
34 requirements: HashSet<String>,
35 config: Config,
35 config: Config,
36 dirstate_parents: LazyCell<DirstateParents>,
36 dirstate_parents: LazyCell<DirstateParents>,
37 dirstate_map: LazyCell<OwningDirstateMap>,
37 dirstate_map: LazyCell<OwningDirstateMap>,
38 changelog: LazyCell<Changelog>,
38 changelog: LazyCell<Changelog>,
39 manifestlog: LazyCell<Manifestlog>,
39 manifestlog: LazyCell<Manifestlog>,
40 }
40 }
41
41
42 #[derive(Debug, derive_more::From)]
42 #[derive(Debug, derive_more::From)]
43 pub enum RepoError {
43 pub enum RepoError {
44 NotFound {
44 NotFound {
45 at: PathBuf,
45 at: PathBuf,
46 },
46 },
47 #[from]
47 #[from]
48 ConfigParseError(ConfigParseError),
48 ConfigParseError(ConfigParseError),
49 #[from]
49 #[from]
50 Other(HgError),
50 Other(HgError),
51 }
51 }
52
52
53 impl From<ConfigError> for RepoError {
53 impl From<ConfigError> for RepoError {
54 fn from(error: ConfigError) -> Self {
54 fn from(error: ConfigError) -> Self {
55 match error {
55 match error {
56 ConfigError::Parse(error) => error.into(),
56 ConfigError::Parse(error) => error.into(),
57 ConfigError::Other(error) => error.into(),
57 ConfigError::Other(error) => error.into(),
58 }
58 }
59 }
59 }
60 }
60 }
61
61
62 impl Repo {
62 impl Repo {
63 /// tries to find nearest repository root in current working directory or
63 /// tries to find nearest repository root in current working directory or
64 /// its ancestors
64 /// its ancestors
65 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
65 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
66 let current_directory = crate::utils::current_dir()?;
66 let current_directory = crate::utils::current_dir()?;
67 // ancestors() is inclusive: it first yields `current_directory`
67 // ancestors() is inclusive: it first yields `current_directory`
68 // as-is.
68 // as-is.
69 for ancestor in current_directory.ancestors() {
69 for ancestor in current_directory.ancestors() {
70 if is_dir(ancestor.join(".hg"))? {
70 if is_dir(ancestor.join(".hg"))? {
71 return Ok(ancestor.to_path_buf());
71 return Ok(ancestor.to_path_buf());
72 }
72 }
73 }
73 }
74 return Err(RepoError::NotFound {
74 return Err(RepoError::NotFound {
75 at: current_directory,
75 at: current_directory,
76 });
76 });
77 }
77 }
78
78
79 /// Find a repository, either at the given path (which must contain a `.hg`
79 /// Find a repository, either at the given path (which must contain a `.hg`
80 /// sub-directory) or by searching the current directory and its
80 /// sub-directory) or by searching the current directory and its
81 /// ancestors.
81 /// ancestors.
82 ///
82 ///
83 /// A method with two very different "modes" like this usually a code smell
83 /// A method with two very different "modes" like this usually a code smell
84 /// to make two methods instead, but in this case an `Option` is what rhg
84 /// to make two methods instead, but in this case an `Option` is what rhg
85 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
85 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
86 /// Having two methods would just move that `if` to almost all callers.
86 /// Having two methods would just move that `if` to almost all callers.
87 pub fn find(
87 pub fn find(
88 config: &Config,
88 config: &Config,
89 explicit_path: Option<PathBuf>,
89 explicit_path: Option<PathBuf>,
90 ) -> Result<Self, RepoError> {
90 ) -> Result<Self, RepoError> {
91 if let Some(root) = explicit_path {
91 if let Some(root) = explicit_path {
92 if is_dir(root.join(".hg"))? {
92 if is_dir(root.join(".hg"))? {
93 Self::new_at_path(root.to_owned(), config)
93 Self::new_at_path(root.to_owned(), config)
94 } else if is_file(&root)? {
94 } else if is_file(&root)? {
95 Err(HgError::unsupported("bundle repository").into())
95 Err(HgError::unsupported("bundle repository").into())
96 } else {
96 } else {
97 Err(RepoError::NotFound {
97 Err(RepoError::NotFound {
98 at: root.to_owned(),
98 at: root.to_owned(),
99 })
99 })
100 }
100 }
101 } else {
101 } else {
102 let root = Self::find_repo_root()?;
102 let root = Self::find_repo_root()?;
103 Self::new_at_path(root, config)
103 Self::new_at_path(root, config)
104 }
104 }
105 }
105 }
106
106
107 /// To be called after checking that `.hg` is a sub-directory
107 /// To be called after checking that `.hg` is a sub-directory
108 fn new_at_path(
108 fn new_at_path(
109 working_directory: PathBuf,
109 working_directory: PathBuf,
110 config: &Config,
110 config: &Config,
111 ) -> Result<Self, RepoError> {
111 ) -> Result<Self, RepoError> {
112 let dot_hg = working_directory.join(".hg");
112 let dot_hg = working_directory.join(".hg");
113
113
114 let mut repo_config_files = Vec::new();
114 let mut repo_config_files = Vec::new();
115 repo_config_files.push(dot_hg.join("hgrc"));
115 repo_config_files.push(dot_hg.join("hgrc"));
116 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
116 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
117
117
118 let hg_vfs = Vfs { base: &dot_hg };
118 let hg_vfs = Vfs { base: &dot_hg };
119 let mut reqs = requirements::load_if_exists(hg_vfs)?;
119 let mut reqs = requirements::load_if_exists(hg_vfs)?;
120 let relative =
120 let relative =
121 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
121 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
122 let shared =
122 let shared =
123 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
123 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
124
124
125 // From `mercurial/localrepo.py`:
125 // From `mercurial/localrepo.py`:
126 //
126 //
127 // if .hg/requires contains the sharesafe requirement, it means
127 // if .hg/requires contains the sharesafe requirement, it means
128 // there exists a `.hg/store/requires` too and we should read it
128 // there exists a `.hg/store/requires` too and we should read it
129 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
129 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
130 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
130 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
131 // is not present, refer checkrequirementscompat() for that
131 // is not present, refer checkrequirementscompat() for that
132 //
132 //
133 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
133 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
134 // repository was shared the old way. We check the share source
134 // repository was shared the old way. We check the share source
135 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
135 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
136 // current repository needs to be reshared
136 // current repository needs to be reshared
137 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
137 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
138
138
139 let store_path;
139 let store_path;
140 if !shared {
140 if !shared {
141 store_path = dot_hg.join("store");
141 store_path = dot_hg.join("store");
142 } else {
142 } else {
143 let bytes = hg_vfs.read("sharedpath")?;
143 let bytes = hg_vfs.read("sharedpath")?;
144 let mut shared_path =
144 let mut shared_path =
145 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
145 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
146 .to_owned();
146 .to_owned();
147 if relative {
147 if relative {
148 shared_path = dot_hg.join(shared_path)
148 shared_path = dot_hg.join(shared_path)
149 }
149 }
150 if !is_dir(&shared_path)? {
150 if !is_dir(&shared_path)? {
151 return Err(HgError::corrupted(format!(
151 return Err(HgError::corrupted(format!(
152 ".hg/sharedpath points to nonexistent directory {}",
152 ".hg/sharedpath points to nonexistent directory {}",
153 shared_path.display()
153 shared_path.display()
154 ))
154 ))
155 .into());
155 .into());
156 }
156 }
157
157
158 store_path = shared_path.join("store");
158 store_path = shared_path.join("store");
159
159
160 let source_is_share_safe =
160 let source_is_share_safe =
161 requirements::load(Vfs { base: &shared_path })?
161 requirements::load(Vfs { base: &shared_path })?
162 .contains(requirements::SHARESAFE_REQUIREMENT);
162 .contains(requirements::SHARESAFE_REQUIREMENT);
163
163
164 if share_safe != source_is_share_safe {
164 if share_safe != source_is_share_safe {
165 return Err(HgError::unsupported("share-safe mismatch").into());
165 return Err(HgError::unsupported("share-safe mismatch").into());
166 }
166 }
167
167
168 if share_safe {
168 if share_safe {
169 repo_config_files.insert(0, shared_path.join("hgrc"))
169 repo_config_files.insert(0, shared_path.join("hgrc"))
170 }
170 }
171 }
171 }
172 if share_safe {
172 if share_safe {
173 reqs.extend(requirements::load(Vfs { base: &store_path })?);
173 reqs.extend(requirements::load(Vfs { base: &store_path })?);
174 }
174 }
175
175
176 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
176 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
177 config.combine_with_repo(&repo_config_files)?
177 config.combine_with_repo(&repo_config_files)?
178 } else {
178 } else {
179 config.clone()
179 config.clone()
180 };
180 };
181
181
182 let repo = Self {
182 let repo = Self {
183 requirements: reqs,
183 requirements: reqs,
184 working_directory,
184 working_directory,
185 store: store_path,
185 store: store_path,
186 dot_hg,
186 dot_hg,
187 config: repo_config,
187 config: repo_config,
188 dirstate_parents: LazyCell::new(),
188 dirstate_parents: LazyCell::new(),
189 dirstate_map: LazyCell::new(),
189 dirstate_map: LazyCell::new(),
190 changelog: LazyCell::new(),
190 changelog: LazyCell::new(),
191 manifestlog: LazyCell::new(),
191 manifestlog: LazyCell::new(),
192 };
192 };
193
193
194 requirements::check(&repo)?;
194 requirements::check(&repo)?;
195
195
196 Ok(repo)
196 Ok(repo)
197 }
197 }
198
198
199 pub fn working_directory_path(&self) -> &Path {
199 pub fn working_directory_path(&self) -> &Path {
200 &self.working_directory
200 &self.working_directory
201 }
201 }
202
202
203 pub fn requirements(&self) -> &HashSet<String> {
203 pub fn requirements(&self) -> &HashSet<String> {
204 &self.requirements
204 &self.requirements
205 }
205 }
206
206
207 pub fn config(&self) -> &Config {
207 pub fn config(&self) -> &Config {
208 &self.config
208 &self.config
209 }
209 }
210
210
211 /// For accessing repository files (in `.hg`), except for the store
211 /// For accessing repository files (in `.hg`), except for the store
212 /// (`.hg/store`).
212 /// (`.hg/store`).
213 pub fn hg_vfs(&self) -> Vfs<'_> {
213 pub fn hg_vfs(&self) -> Vfs<'_> {
214 Vfs { base: &self.dot_hg }
214 Vfs { base: &self.dot_hg }
215 }
215 }
216
216
217 /// For accessing repository store files (in `.hg/store`)
217 /// For accessing repository store files (in `.hg/store`)
218 pub fn store_vfs(&self) -> Vfs<'_> {
218 pub fn store_vfs(&self) -> Vfs<'_> {
219 Vfs { base: &self.store }
219 Vfs { base: &self.store }
220 }
220 }
221
221
222 /// For accessing the working copy
222 /// For accessing the working copy
223 pub fn working_directory_vfs(&self) -> Vfs<'_> {
223 pub fn working_directory_vfs(&self) -> Vfs<'_> {
224 Vfs {
224 Vfs {
225 base: &self.working_directory,
225 base: &self.working_directory,
226 }
226 }
227 }
227 }
228
228
229 pub fn try_with_wlock_no_wait<R>(
229 pub fn try_with_wlock_no_wait<R>(
230 &self,
230 &self,
231 f: impl FnOnce() -> R,
231 f: impl FnOnce() -> R,
232 ) -> Result<R, LockError> {
232 ) -> Result<R, LockError> {
233 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
233 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
234 }
234 }
235
235
236 pub fn has_dirstate_v2(&self) -> bool {
236 pub fn has_dirstate_v2(&self) -> bool {
237 self.requirements
237 self.requirements
238 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
238 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
239 }
239 }
240
240
241 pub fn has_sparse(&self) -> bool {
241 pub fn has_sparse(&self) -> bool {
242 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
242 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
243 }
243 }
244
244
245 pub fn has_narrow(&self) -> bool {
245 pub fn has_narrow(&self) -> bool {
246 self.requirements.contains(requirements::NARROW_REQUIREMENT)
246 self.requirements.contains(requirements::NARROW_REQUIREMENT)
247 }
247 }
248
248
249 pub fn has_nodemap(&self) -> bool {
249 pub fn has_nodemap(&self) -> bool {
250 self.requirements
250 self.requirements
251 .contains(requirements::NODEMAP_REQUIREMENT)
251 .contains(requirements::NODEMAP_REQUIREMENT)
252 }
252 }
253
253
254 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
254 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
255 Ok(self
255 Ok(self
256 .hg_vfs()
256 .hg_vfs()
257 .read("dirstate")
257 .read("dirstate")
258 .io_not_found_as_none()?
258 .io_not_found_as_none()?
259 .unwrap_or(Vec::new()))
259 .unwrap_or(Vec::new()))
260 }
260 }
261
261
262 fn dirstate_identity(&self) -> Result<Option<u64>, HgError> {
263 use std::os::unix::fs::MetadataExt;
264 Ok(self
265 .hg_vfs()
266 .symlink_metadata("dirstate")
267 .io_not_found_as_none()?
268 .map(|meta| meta.ino()))
269 }
270
262 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
271 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
263 Ok(*self
272 Ok(*self
264 .dirstate_parents
273 .dirstate_parents
265 .get_or_init(|| self.read_dirstate_parents())?)
274 .get_or_init(|| self.read_dirstate_parents())?)
266 }
275 }
267
276
268 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
277 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
269 let dirstate = self.dirstate_file_contents()?;
278 let dirstate = self.dirstate_file_contents()?;
270 let parents = if dirstate.is_empty() {
279 let parents = if dirstate.is_empty() {
271 DirstateParents::NULL
280 DirstateParents::NULL
272 } else if self.has_dirstate_v2() {
281 } else if self.has_dirstate_v2() {
273 let docket =
282 let docket =
274 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
283 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
275 docket.parents()
284 docket.parents()
276 } else {
285 } else {
277 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
286 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
278 .clone()
287 .clone()
279 };
288 };
280 self.dirstate_parents.set(parents);
289 self.dirstate_parents.set(parents);
281 Ok(parents)
290 Ok(parents)
282 }
291 }
283
292
284 /// Returns the information read from the dirstate docket necessary to
293 /// Returns the information read from the dirstate docket necessary to
285 /// check if the data file has been updated/deleted by another process
294 /// check if the data file has been updated/deleted by another process
286 /// since we last read the dirstate.
295 /// since we last read the dirstate.
287 /// Namely, the data file uuid and the data size.
296 /// Namely, the inode, data file uuid and the data size.
288 fn get_dirstate_data_file_integrity(
297 fn get_dirstate_data_file_integrity(
289 &self,
298 &self,
290 ) -> Result<(Option<Vec<u8>>, usize), HgError> {
299 ) -> Result<(Option<u64>, Option<Vec<u8>>, usize), HgError> {
291 assert!(
300 assert!(
292 self.has_dirstate_v2(),
301 self.has_dirstate_v2(),
293 "accessing dirstate data file ID without dirstate-v2"
302 "accessing dirstate data file ID without dirstate-v2"
294 );
303 );
304 // Get the identity before the contents since we could have a race
305 // between the two. Having an identity that is too old is fine, but
306 // one that is younger than the content change is bad.
307 let identity = self.dirstate_identity()?;
295 let dirstate = self.dirstate_file_contents()?;
308 let dirstate = self.dirstate_file_contents()?;
296 if dirstate.is_empty() {
309 if dirstate.is_empty() {
297 self.dirstate_parents.set(DirstateParents::NULL);
310 self.dirstate_parents.set(DirstateParents::NULL);
298 Ok((None, 0))
311 Ok((identity, None, 0))
299 } else {
312 } else {
300 let docket =
313 let docket =
301 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
314 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
302 self.dirstate_parents.set(docket.parents());
315 self.dirstate_parents.set(docket.parents());
303 Ok((Some(docket.uuid.to_owned()), docket.data_size()))
316 Ok((identity, Some(docket.uuid.to_owned()), docket.data_size()))
304 }
317 }
305 }
318 }
306
319
307 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
320 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
308 if self.has_dirstate_v2() {
321 if self.has_dirstate_v2() {
309 // The v2 dirstate is split into a docket and a data file.
322 // The v2 dirstate is split into a docket and a data file.
310 // Since we don't always take the `wlock` to read it
323 // Since we don't always take the `wlock` to read it
311 // (like in `hg status`), it is susceptible to races.
324 // (like in `hg status`), it is susceptible to races.
312 // A simple retry method should be enough since full rewrites
325 // A simple retry method should be enough since full rewrites
313 // only happen when too much garbage data is present and
326 // only happen when too much garbage data is present and
314 // this race is unlikely.
327 // this race is unlikely.
315 let mut tries = 0;
328 let mut tries = 0;
316
329
317 while tries < V2_MAX_READ_ATTEMPTS {
330 while tries < V2_MAX_READ_ATTEMPTS {
318 tries += 1;
331 tries += 1;
319 match self.read_docket_and_data_file() {
332 match self.read_docket_and_data_file() {
320 Ok(m) => {
333 Ok(m) => {
321 return Ok(m);
334 return Ok(m);
322 }
335 }
323 Err(e) => match e {
336 Err(e) => match e {
324 DirstateError::Common(HgError::RaceDetected(
337 DirstateError::Common(HgError::RaceDetected(
325 context,
338 context,
326 )) => {
339 )) => {
327 log::info!(
340 log::info!(
328 "dirstate read race detected {} (retry {}/{})",
341 "dirstate read race detected {} (retry {}/{})",
329 context,
342 context,
330 tries,
343 tries,
331 V2_MAX_READ_ATTEMPTS,
344 V2_MAX_READ_ATTEMPTS,
332 );
345 );
333 continue;
346 continue;
334 }
347 }
335 _ => return Err(e.into()),
348 _ => return Err(e.into()),
336 },
349 },
337 }
350 }
338 }
351 }
339 let error = HgError::abort(
352 let error = HgError::abort(
340 format!("dirstate read race happened {tries} times in a row"),
353 format!("dirstate read race happened {tries} times in a row"),
341 255,
354 255,
342 None,
355 None,
343 );
356 );
344 return Err(DirstateError::Common(error));
357 return Err(DirstateError::Common(error));
345 } else {
358 } else {
346 debug_wait_for_file_or_print(
359 debug_wait_for_file_or_print(
347 self.config(),
360 self.config(),
348 "dirstate.pre-read-file",
361 "dirstate.pre-read-file",
349 );
362 );
363 let identity = self.dirstate_identity()?;
350 let dirstate_file_contents = self.dirstate_file_contents()?;
364 let dirstate_file_contents = self.dirstate_file_contents()?;
351 return if dirstate_file_contents.is_empty() {
365 return if dirstate_file_contents.is_empty() {
352 self.dirstate_parents.set(DirstateParents::NULL);
366 self.dirstate_parents.set(DirstateParents::NULL);
353 Ok(OwningDirstateMap::new_empty(Vec::new()))
367 Ok(OwningDirstateMap::new_empty(Vec::new()))
354 } else {
368 } else {
355 let (map, parents) =
369 let (map, parents) = OwningDirstateMap::new_v1(
356 OwningDirstateMap::new_v1(dirstate_file_contents)?;
370 dirstate_file_contents,
371 identity,
372 )?;
357 self.dirstate_parents.set(parents);
373 self.dirstate_parents.set(parents);
358 Ok(map)
374 Ok(map)
359 };
375 };
360 }
376 }
361 }
377 }
362
378
363 fn read_docket_and_data_file(
379 fn read_docket_and_data_file(
364 &self,
380 &self,
365 ) -> Result<OwningDirstateMap, DirstateError> {
381 ) -> Result<OwningDirstateMap, DirstateError> {
366 debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
382 debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
367 let dirstate_file_contents = self.dirstate_file_contents()?;
383 let dirstate_file_contents = self.dirstate_file_contents()?;
384 let identity = self.dirstate_identity()?;
368 if dirstate_file_contents.is_empty() {
385 if dirstate_file_contents.is_empty() {
369 self.dirstate_parents.set(DirstateParents::NULL);
386 self.dirstate_parents.set(DirstateParents::NULL);
370 return Ok(OwningDirstateMap::new_empty(Vec::new()));
387 return Ok(OwningDirstateMap::new_empty(Vec::new()));
371 }
388 }
372 let docket = crate::dirstate_tree::on_disk::read_docket(
389 let docket = crate::dirstate_tree::on_disk::read_docket(
373 &dirstate_file_contents,
390 &dirstate_file_contents,
374 )?;
391 )?;
375 debug_wait_for_file_or_print(
392 debug_wait_for_file_or_print(
376 self.config(),
393 self.config(),
377 "dirstate.post-docket-read-file",
394 "dirstate.post-docket-read-file",
378 );
395 );
379 self.dirstate_parents.set(docket.parents());
396 self.dirstate_parents.set(docket.parents());
380 let uuid = docket.uuid.to_owned();
397 let uuid = docket.uuid.to_owned();
381 let data_size = docket.data_size();
398 let data_size = docket.data_size();
382
399
383 let context = "between reading dirstate docket and data file";
400 let context = "between reading dirstate docket and data file";
384 let race_error = HgError::RaceDetected(context.into());
401 let race_error = HgError::RaceDetected(context.into());
385 let metadata = docket.tree_metadata();
402 let metadata = docket.tree_metadata();
386
403
387 let mut map = if crate::vfs::is_on_nfs_mount(docket.data_filename()) {
404 let mut map = if crate::vfs::is_on_nfs_mount(docket.data_filename()) {
388 // Don't mmap on NFS to prevent `SIGBUS` error on deletion
405 // Don't mmap on NFS to prevent `SIGBUS` error on deletion
389 let contents = self.hg_vfs().read(docket.data_filename());
406 let contents = self.hg_vfs().read(docket.data_filename());
390 let contents = match contents {
407 let contents = match contents {
391 Ok(c) => c,
408 Ok(c) => c,
392 Err(HgError::IoError { error, context }) => {
409 Err(HgError::IoError { error, context }) => {
393 match error.raw_os_error().expect("real os error") {
410 match error.raw_os_error().expect("real os error") {
394 // 2 = ENOENT, No such file or directory
411 // 2 = ENOENT, No such file or directory
395 // 116 = ESTALE, Stale NFS file handle
412 // 116 = ESTALE, Stale NFS file handle
396 //
413 //
397 // TODO match on `error.kind()` when
414 // TODO match on `error.kind()` when
398 // `ErrorKind::StaleNetworkFileHandle` is stable.
415 // `ErrorKind::StaleNetworkFileHandle` is stable.
399 2 | 116 => {
416 2 | 116 => {
400 // Race where the data file was deleted right after
417 // Race where the data file was deleted right after
401 // we read the docket, try again
418 // we read the docket, try again
402 return Err(race_error.into());
419 return Err(race_error.into());
403 }
420 }
404 _ => {
421 _ => {
405 return Err(
422 return Err(
406 HgError::IoError { error, context }.into()
423 HgError::IoError { error, context }.into()
407 )
424 )
408 }
425 }
409 }
426 }
410 }
427 }
411 Err(e) => return Err(e.into()),
428 Err(e) => return Err(e.into()),
412 };
429 };
413 OwningDirstateMap::new_v2(contents, data_size, metadata, uuid)
430 OwningDirstateMap::new_v2(
431 contents, data_size, metadata, uuid, identity,
432 )
414 } else {
433 } else {
415 match self
434 match self
416 .hg_vfs()
435 .hg_vfs()
417 .mmap_open(docket.data_filename())
436 .mmap_open(docket.data_filename())
418 .io_not_found_as_none()
437 .io_not_found_as_none()
419 {
438 {
420 Ok(Some(data_mmap)) => OwningDirstateMap::new_v2(
439 Ok(Some(data_mmap)) => OwningDirstateMap::new_v2(
421 data_mmap, data_size, metadata, uuid,
440 data_mmap, data_size, metadata, uuid, identity,
422 ),
441 ),
423 Ok(None) => {
442 Ok(None) => {
424 // Race where the data file was deleted right after we
443 // Race where the data file was deleted right after we
425 // read the docket, try again
444 // read the docket, try again
426 return Err(race_error.into());
445 return Err(race_error.into());
427 }
446 }
428 Err(e) => return Err(e.into()),
447 Err(e) => return Err(e.into()),
429 }
448 }
430 }?;
449 }?;
431
450
432 let write_mode_config = self
451 let write_mode_config = self
433 .config()
452 .config()
434 .get_str(b"devel", b"dirstate.v2.data_update_mode")
453 .get_str(b"devel", b"dirstate.v2.data_update_mode")
435 .unwrap_or(Some("auto"))
454 .unwrap_or(Some("auto"))
436 .unwrap_or("auto"); // don't bother for devel options
455 .unwrap_or("auto"); // don't bother for devel options
437 let write_mode = match write_mode_config {
456 let write_mode = match write_mode_config {
438 "auto" => DirstateMapWriteMode::Auto,
457 "auto" => DirstateMapWriteMode::Auto,
439 "force-new" => DirstateMapWriteMode::ForceNewDataFile,
458 "force-new" => DirstateMapWriteMode::ForceNewDataFile,
440 "force-append" => DirstateMapWriteMode::ForceAppend,
459 "force-append" => DirstateMapWriteMode::ForceAppend,
441 _ => DirstateMapWriteMode::Auto,
460 _ => DirstateMapWriteMode::Auto,
442 };
461 };
443
462
444 map.with_dmap_mut(|m| m.set_write_mode(write_mode));
463 map.with_dmap_mut(|m| m.set_write_mode(write_mode));
445
464
446 Ok(map)
465 Ok(map)
447 }
466 }
448
467
449 pub fn dirstate_map(
468 pub fn dirstate_map(
450 &self,
469 &self,
451 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
470 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
452 self.dirstate_map.get_or_init(|| self.new_dirstate_map())
471 self.dirstate_map.get_or_init(|| self.new_dirstate_map())
453 }
472 }
454
473
455 pub fn dirstate_map_mut(
474 pub fn dirstate_map_mut(
456 &self,
475 &self,
457 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
476 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
458 self.dirstate_map
477 self.dirstate_map
459 .get_mut_or_init(|| self.new_dirstate_map())
478 .get_mut_or_init(|| self.new_dirstate_map())
460 }
479 }
461
480
462 fn new_changelog(&self) -> Result<Changelog, HgError> {
481 fn new_changelog(&self) -> Result<Changelog, HgError> {
463 Changelog::open(&self.store_vfs(), self.has_nodemap())
482 Changelog::open(&self.store_vfs(), self.has_nodemap())
464 }
483 }
465
484
466 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
485 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
467 self.changelog.get_or_init(|| self.new_changelog())
486 self.changelog.get_or_init(|| self.new_changelog())
468 }
487 }
469
488
470 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
489 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
471 self.changelog.get_mut_or_init(|| self.new_changelog())
490 self.changelog.get_mut_or_init(|| self.new_changelog())
472 }
491 }
473
492
474 fn new_manifestlog(&self) -> Result<Manifestlog, HgError> {
493 fn new_manifestlog(&self) -> Result<Manifestlog, HgError> {
475 Manifestlog::open(&self.store_vfs(), self.has_nodemap())
494 Manifestlog::open(&self.store_vfs(), self.has_nodemap())
476 }
495 }
477
496
478 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
497 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
479 self.manifestlog.get_or_init(|| self.new_manifestlog())
498 self.manifestlog.get_or_init(|| self.new_manifestlog())
480 }
499 }
481
500
482 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
501 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
483 self.manifestlog.get_mut_or_init(|| self.new_manifestlog())
502 self.manifestlog.get_mut_or_init(|| self.new_manifestlog())
484 }
503 }
485
504
486 /// Returns the manifest of the *changeset* with the given node ID
505 /// Returns the manifest of the *changeset* with the given node ID
487 pub fn manifest_for_node(
506 pub fn manifest_for_node(
488 &self,
507 &self,
489 node: impl Into<NodePrefix>,
508 node: impl Into<NodePrefix>,
490 ) -> Result<Manifest, RevlogError> {
509 ) -> Result<Manifest, RevlogError> {
491 self.manifestlog()?.data_for_node(
510 self.manifestlog()?.data_for_node(
492 self.changelog()?
511 self.changelog()?
493 .data_for_node(node.into())?
512 .data_for_node(node.into())?
494 .manifest_node()?
513 .manifest_node()?
495 .into(),
514 .into(),
496 )
515 )
497 }
516 }
498
517
499 /// Returns the manifest of the *changeset* with the given revision number
518 /// Returns the manifest of the *changeset* with the given revision number
500 pub fn manifest_for_rev(
519 pub fn manifest_for_rev(
501 &self,
520 &self,
502 revision: Revision,
521 revision: Revision,
503 ) -> Result<Manifest, RevlogError> {
522 ) -> Result<Manifest, RevlogError> {
504 self.manifestlog()?.data_for_node(
523 self.manifestlog()?.data_for_node(
505 self.changelog()?
524 self.changelog()?
506 .data_for_rev(revision)?
525 .data_for_rev(revision)?
507 .manifest_node()?
526 .manifest_node()?
508 .into(),
527 .into(),
509 )
528 )
510 }
529 }
511
530
512 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
531 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
513 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
532 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
514 Ok(entry.tracked())
533 Ok(entry.tracked())
515 } else {
534 } else {
516 Ok(false)
535 Ok(false)
517 }
536 }
518 }
537 }
519
538
520 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
539 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
521 Filelog::open(self, path)
540 Filelog::open(self, path)
522 }
541 }
523
542
524 /// Write to disk any updates that were made through `dirstate_map_mut`.
543 /// Write to disk any updates that were made through `dirstate_map_mut`.
525 ///
544 ///
526 /// The "wlock" must be held while calling this.
545 /// The "wlock" must be held while calling this.
527 /// See for example `try_with_wlock_no_wait`.
546 /// See for example `try_with_wlock_no_wait`.
528 ///
547 ///
529 /// TODO: have a `WritableRepo` type only accessible while holding the
548 /// TODO: have a `WritableRepo` type only accessible while holding the
530 /// lock?
549 /// lock?
531 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
550 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
532 let map = self.dirstate_map()?;
551 let map = self.dirstate_map()?;
533 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
552 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
534 // it’s unset
553 // it’s unset
535 let parents = self.dirstate_parents()?;
554 let parents = self.dirstate_parents()?;
536 let (packed_dirstate, old_uuid_to_remove) = if self.has_dirstate_v2() {
555 let (packed_dirstate, old_uuid_to_remove) = if self.has_dirstate_v2() {
537 let (uuid, data_size) = self.get_dirstate_data_file_integrity()?;
556 let (identity, uuid, data_size) =
557 self.get_dirstate_data_file_integrity()?;
558 let identity_changed = identity != map.old_identity();
538 let uuid_changed = uuid.as_deref() != map.old_uuid();
559 let uuid_changed = uuid.as_deref() != map.old_uuid();
539 let data_length_changed = data_size != map.old_data_size();
560 let data_length_changed = data_size != map.old_data_size();
540
561
541 if uuid_changed || data_length_changed {
562 if identity_changed || uuid_changed || data_length_changed {
542 // If uuid or length changed since last disk read, don't write.
563 // If any of identity, uuid or length have changed since
564 // last disk read, don't write.
543 // This is fine because either we're in a command that doesn't
565 // This is fine because either we're in a command that doesn't
544 // write anything too important (like `hg status`), or we're in
566 // write anything too important (like `hg status`), or we're in
545 // `hg add` and we're supposed to have taken the lock before
567 // `hg add` and we're supposed to have taken the lock before
546 // reading anyway.
568 // reading anyway.
547 //
569 //
548 // TODO complain loudly if we've changed anything important
570 // TODO complain loudly if we've changed anything important
549 // without taking the lock.
571 // without taking the lock.
550 // (see `hg help config.format.use-dirstate-tracked-hint`)
572 // (see `hg help config.format.use-dirstate-tracked-hint`)
551 log::debug!(
573 log::debug!(
552 "dirstate has changed since last read, not updating."
574 "dirstate has changed since last read, not updating."
553 );
575 );
554 return Ok(());
576 return Ok(());
555 }
577 }
556
578
557 let uuid_opt = map.old_uuid();
579 let uuid_opt = map.old_uuid();
558 let write_mode = if uuid_opt.is_some() {
580 let write_mode = if uuid_opt.is_some() {
559 DirstateMapWriteMode::Auto
581 DirstateMapWriteMode::Auto
560 } else {
582 } else {
561 DirstateMapWriteMode::ForceNewDataFile
583 DirstateMapWriteMode::ForceNewDataFile
562 };
584 };
563 let (data, tree_metadata, append, old_data_size) =
585 let (data, tree_metadata, append, old_data_size) =
564 map.pack_v2(write_mode)?;
586 map.pack_v2(write_mode)?;
565
587
566 // Reuse the uuid, or generate a new one, keeping the old for
588 // Reuse the uuid, or generate a new one, keeping the old for
567 // deletion.
589 // deletion.
568 let (uuid, old_uuid) = match uuid_opt {
590 let (uuid, old_uuid) = match uuid_opt {
569 Some(uuid) => {
591 Some(uuid) => {
570 let as_str = std::str::from_utf8(uuid)
592 let as_str = std::str::from_utf8(uuid)
571 .map_err(|_| {
593 .map_err(|_| {
572 HgError::corrupted(
594 HgError::corrupted(
573 "non-UTF-8 dirstate data file ID",
595 "non-UTF-8 dirstate data file ID",
574 )
596 )
575 })?
597 })?
576 .to_owned();
598 .to_owned();
577 if append {
599 if append {
578 (as_str, None)
600 (as_str, None)
579 } else {
601 } else {
580 (DirstateDocket::new_uid(), Some(as_str))
602 (DirstateDocket::new_uid(), Some(as_str))
581 }
603 }
582 }
604 }
583 None => (DirstateDocket::new_uid(), None),
605 None => (DirstateDocket::new_uid(), None),
584 };
606 };
585
607
586 let data_filename = format!("dirstate.{}", uuid);
608 let data_filename = format!("dirstate.{}", uuid);
587 let data_filename = self.hg_vfs().join(data_filename);
609 let data_filename = self.hg_vfs().join(data_filename);
588 let mut options = std::fs::OpenOptions::new();
610 let mut options = std::fs::OpenOptions::new();
589 options.write(true);
611 options.write(true);
590
612
591 // Why are we not using the O_APPEND flag when appending?
613 // Why are we not using the O_APPEND flag when appending?
592 //
614 //
593 // - O_APPEND makes it trickier to deal with garbage at the end of
615 // - O_APPEND makes it trickier to deal with garbage at the end of
594 // the file, left by a previous uncommitted transaction. By
616 // the file, left by a previous uncommitted transaction. By
595 // starting the write at [old_data_size] we make sure we erase
617 // starting the write at [old_data_size] we make sure we erase
596 // all such garbage.
618 // all such garbage.
597 //
619 //
598 // - O_APPEND requires to special-case 0-byte writes, whereas we
620 // - O_APPEND requires to special-case 0-byte writes, whereas we
599 // don't need that.
621 // don't need that.
600 //
622 //
601 // - Some OSes have bugs in implementation O_APPEND:
623 // - Some OSes have bugs in implementation O_APPEND:
602 // revlog.py talks about a Solaris bug, but we also saw some ZFS
624 // revlog.py talks about a Solaris bug, but we also saw some ZFS
603 // bug: https://github.com/openzfs/zfs/pull/3124,
625 // bug: https://github.com/openzfs/zfs/pull/3124,
604 // https://github.com/openzfs/zfs/issues/13370
626 // https://github.com/openzfs/zfs/issues/13370
605 //
627 //
606 if !append {
628 if !append {
607 log::trace!("creating a new dirstate data file");
629 log::trace!("creating a new dirstate data file");
608 options.create_new(true);
630 options.create_new(true);
609 } else {
631 } else {
610 log::trace!("appending to the dirstate data file");
632 log::trace!("appending to the dirstate data file");
611 }
633 }
612
634
613 let data_size = (|| {
635 let data_size = (|| {
614 // TODO: loop and try another random ID if !append and this
636 // TODO: loop and try another random ID if !append and this
615 // returns `ErrorKind::AlreadyExists`? Collision chance of two
637 // returns `ErrorKind::AlreadyExists`? Collision chance of two
616 // random IDs is one in 2**32
638 // random IDs is one in 2**32
617 let mut file = options.open(&data_filename)?;
639 let mut file = options.open(&data_filename)?;
618 if append {
640 if append {
619 file.seek(SeekFrom::Start(old_data_size as u64))?;
641 file.seek(SeekFrom::Start(old_data_size as u64))?;
620 }
642 }
621 file.write_all(&data)?;
643 file.write_all(&data)?;
622 file.flush()?;
644 file.flush()?;
623 file.seek(SeekFrom::Current(0))
645 file.seek(SeekFrom::Current(0))
624 })()
646 })()
625 .when_writing_file(&data_filename)?;
647 .when_writing_file(&data_filename)?;
626
648
627 let packed_dirstate = DirstateDocket::serialize(
649 let packed_dirstate = DirstateDocket::serialize(
628 parents,
650 parents,
629 tree_metadata,
651 tree_metadata,
630 data_size,
652 data_size,
631 uuid.as_bytes(),
653 uuid.as_bytes(),
632 )
654 )
633 .map_err(|_: std::num::TryFromIntError| {
655 .map_err(|_: std::num::TryFromIntError| {
634 HgError::corrupted("overflow in dirstate docket serialization")
656 HgError::corrupted("overflow in dirstate docket serialization")
635 })?;
657 })?;
636
658
637 (packed_dirstate, old_uuid)
659 (packed_dirstate, old_uuid)
638 } else {
660 } else {
661 let identity = self.dirstate_identity()?;
662 if identity != map.old_identity() {
663 // If identity changed since last disk read, don't write.
664 // This is fine because either we're in a command that doesn't
665 // write anything too important (like `hg status`), or we're in
666 // `hg add` and we're supposed to have taken the lock before
667 // reading anyway.
668 //
669 // TODO complain loudly if we've changed anything important
670 // without taking the lock.
671 // (see `hg help config.format.use-dirstate-tracked-hint`)
672 log::debug!(
673 "dirstate has changed since last read, not updating."
674 );
675 return Ok(());
676 }
639 (map.pack_v1(parents)?, None)
677 (map.pack_v1(parents)?, None)
640 };
678 };
641
679
642 let vfs = self.hg_vfs();
680 let vfs = self.hg_vfs();
643 vfs.atomic_write("dirstate", &packed_dirstate)?;
681 vfs.atomic_write("dirstate", &packed_dirstate)?;
644 if let Some(uuid) = old_uuid_to_remove {
682 if let Some(uuid) = old_uuid_to_remove {
645 // Remove the old data file after the new docket pointing to the
683 // Remove the old data file after the new docket pointing to the
646 // new data file was written.
684 // new data file was written.
647 vfs.remove_file(format!("dirstate.{}", uuid))?;
685 vfs.remove_file(format!("dirstate.{}", uuid))?;
648 }
686 }
649 Ok(())
687 Ok(())
650 }
688 }
651 }
689 }
652
690
653 /// Lazily-initialized component of `Repo` with interior mutability
691 /// Lazily-initialized component of `Repo` with interior mutability
654 ///
692 ///
655 /// This differs from `OnceCell` in that the value can still be "deinitialized"
693 /// This differs from `OnceCell` in that the value can still be "deinitialized"
656 /// later by setting its inner `Option` to `None`. It also takes the
694 /// later by setting its inner `Option` to `None`. It also takes the
657 /// initialization function as an argument when the value is requested, not
695 /// initialization function as an argument when the value is requested, not
658 /// when the instance is created.
696 /// when the instance is created.
659 struct LazyCell<T> {
697 struct LazyCell<T> {
660 value: RefCell<Option<T>>,
698 value: RefCell<Option<T>>,
661 }
699 }
662
700
663 impl<T> LazyCell<T> {
701 impl<T> LazyCell<T> {
664 fn new() -> Self {
702 fn new() -> Self {
665 Self {
703 Self {
666 value: RefCell::new(None),
704 value: RefCell::new(None),
667 }
705 }
668 }
706 }
669
707
670 fn set(&self, value: T) {
708 fn set(&self, value: T) {
671 *self.value.borrow_mut() = Some(value)
709 *self.value.borrow_mut() = Some(value)
672 }
710 }
673
711
674 fn get_or_init<E>(
712 fn get_or_init<E>(
675 &self,
713 &self,
676 init: impl Fn() -> Result<T, E>,
714 init: impl Fn() -> Result<T, E>,
677 ) -> Result<Ref<T>, E> {
715 ) -> Result<Ref<T>, E> {
678 let mut borrowed = self.value.borrow();
716 let mut borrowed = self.value.borrow();
679 if borrowed.is_none() {
717 if borrowed.is_none() {
680 drop(borrowed);
718 drop(borrowed);
681 // Only use `borrow_mut` if it is really needed to avoid panic in
719 // Only use `borrow_mut` if it is really needed to avoid panic in
682 // case there is another outstanding borrow but mutation is not
720 // case there is another outstanding borrow but mutation is not
683 // needed.
721 // needed.
684 *self.value.borrow_mut() = Some(init()?);
722 *self.value.borrow_mut() = Some(init()?);
685 borrowed = self.value.borrow()
723 borrowed = self.value.borrow()
686 }
724 }
687 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
725 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
688 }
726 }
689
727
690 fn get_mut_or_init<E>(
728 fn get_mut_or_init<E>(
691 &self,
729 &self,
692 init: impl Fn() -> Result<T, E>,
730 init: impl Fn() -> Result<T, E>,
693 ) -> Result<RefMut<T>, E> {
731 ) -> Result<RefMut<T>, E> {
694 let mut borrowed = self.value.borrow_mut();
732 let mut borrowed = self.value.borrow_mut();
695 if borrowed.is_none() {
733 if borrowed.is_none() {
696 *borrowed = Some(init()?);
734 *borrowed = Some(init()?);
697 }
735 }
698 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
736 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
699 }
737 }
700 }
738 }
@@ -1,561 +1,567 b''
1 // dirstate_map.rs
1 // dirstate_map.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 //! Bindings for the `hg::dirstate::dirstate_map` file provided by the
8 //! Bindings for the `hg::dirstate::dirstate_map` file provided by the
9 //! `hg-core` package.
9 //! `hg-core` package.
10
10
11 use std::cell::{RefCell, RefMut};
11 use std::cell::{RefCell, RefMut};
12 use std::convert::TryInto;
12 use std::convert::TryInto;
13
13
14 use cpython::{
14 use cpython::{
15 exc, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, PyNone, PyObject,
15 exc, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, PyNone, PyObject,
16 PyResult, Python, PythonObject, ToPyObject, UnsafePyLeaked,
16 PyResult, Python, PythonObject, ToPyObject, UnsafePyLeaked,
17 };
17 };
18 use hg::dirstate::{ParentFileData, TruncatedTimestamp};
18 use hg::dirstate::{ParentFileData, TruncatedTimestamp};
19
19
20 use crate::{
20 use crate::{
21 dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
21 dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
22 dirstate::item::DirstateItem,
22 dirstate::item::DirstateItem,
23 pybytes_deref::PyBytesDeref,
23 pybytes_deref::PyBytesDeref,
24 };
24 };
25 use hg::{
25 use hg::{
26 dirstate::StateMapIter, dirstate_tree::dirstate_map::DirstateMapWriteMode,
26 dirstate::StateMapIter, dirstate_tree::dirstate_map::DirstateMapWriteMode,
27 dirstate_tree::on_disk::DirstateV2ParseError,
27 dirstate_tree::on_disk::DirstateV2ParseError,
28 dirstate_tree::owning::OwningDirstateMap, revlog::Node,
28 dirstate_tree::owning::OwningDirstateMap, revlog::Node,
29 utils::files::normalize_case, utils::hg_path::HgPath, DirstateEntry,
29 utils::files::normalize_case, utils::hg_path::HgPath, DirstateEntry,
30 DirstateError, DirstateParents,
30 DirstateError, DirstateParents,
31 };
31 };
32
32
33 // TODO
33 // TODO
34 // This object needs to share references to multiple members of its Rust
34 // This object needs to share references to multiple members of its Rust
35 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
35 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
36 // Right now `CopyMap` is done, but it needs to have an explicit reference
36 // Right now `CopyMap` is done, but it needs to have an explicit reference
37 // to `RustDirstateMap` which itself needs to have an encapsulation for
37 // to `RustDirstateMap` which itself needs to have an encapsulation for
38 // every method in `CopyMap` (copymapcopy, etc.).
38 // every method in `CopyMap` (copymapcopy, etc.).
39 // This is ugly and hard to maintain.
39 // This is ugly and hard to maintain.
40 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
40 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
41 // `py_class!` is already implemented and does not mention
41 // `py_class!` is already implemented and does not mention
42 // `RustDirstateMap`, rightfully so.
42 // `RustDirstateMap`, rightfully so.
43 // All attributes also have to have a separate refcount data attribute for
43 // All attributes also have to have a separate refcount data attribute for
44 // leaks, with all methods that go along for reference sharing.
44 // leaks, with all methods that go along for reference sharing.
45 py_class!(pub class DirstateMap |py| {
45 py_class!(pub class DirstateMap |py| {
46 @shared data inner: OwningDirstateMap;
46 @shared data inner: OwningDirstateMap;
47
47
48 /// Returns a `(dirstate_map, parents)` tuple
48 /// Returns a `(dirstate_map, parents)` tuple
49 @staticmethod
49 @staticmethod
50 def new_v1(
50 def new_v1(
51 on_disk: PyBytes,
51 on_disk: PyBytes,
52 identity: Option<u64>,
52 ) -> PyResult<PyObject> {
53 ) -> PyResult<PyObject> {
53 let on_disk = PyBytesDeref::new(py, on_disk);
54 let on_disk = PyBytesDeref::new(py, on_disk);
54 let (map, parents) = OwningDirstateMap::new_v1(on_disk)
55 let (map, parents) = OwningDirstateMap::new_v1(on_disk, identity)
55 .map_err(|e| dirstate_error(py, e))?;
56 .map_err(|e| dirstate_error(py, e))?;
56 let map = Self::create_instance(py, map)?;
57 let map = Self::create_instance(py, map)?;
57 let p1 = PyBytes::new(py, parents.p1.as_bytes());
58 let p1 = PyBytes::new(py, parents.p1.as_bytes());
58 let p2 = PyBytes::new(py, parents.p2.as_bytes());
59 let p2 = PyBytes::new(py, parents.p2.as_bytes());
59 let parents = (p1, p2);
60 let parents = (p1, p2);
60 Ok((map, parents).to_py_object(py).into_object())
61 Ok((map, parents).to_py_object(py).into_object())
61 }
62 }
62
63
63 /// Returns a DirstateMap
64 /// Returns a DirstateMap
64 @staticmethod
65 @staticmethod
65 def new_v2(
66 def new_v2(
66 on_disk: PyBytes,
67 on_disk: PyBytes,
67 data_size: usize,
68 data_size: usize,
68 tree_metadata: PyBytes,
69 tree_metadata: PyBytes,
69 uuid: PyBytes,
70 uuid: PyBytes,
71 identity: Option<u64>,
70 ) -> PyResult<PyObject> {
72 ) -> PyResult<PyObject> {
71 let dirstate_error = |e: DirstateError| {
73 let dirstate_error = |e: DirstateError| {
72 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
74 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
73 };
75 };
74 let on_disk = PyBytesDeref::new(py, on_disk);
76 let on_disk = PyBytesDeref::new(py, on_disk);
75 let uuid = uuid.data(py);
77 let uuid = uuid.data(py);
76 let map = OwningDirstateMap::new_v2(
78 let map = OwningDirstateMap::new_v2(
77 on_disk, data_size, tree_metadata.data(py), uuid.to_owned(),
79 on_disk,
80 data_size,
81 tree_metadata.data(py),
82 uuid.to_owned(),
83 identity,
78 ).map_err(dirstate_error)?;
84 ).map_err(dirstate_error)?;
79 let map = Self::create_instance(py, map)?;
85 let map = Self::create_instance(py, map)?;
80 Ok(map.into_object())
86 Ok(map.into_object())
81 }
87 }
82
88
83 /// Returns an empty DirstateMap. Only used for a new dirstate.
89 /// Returns an empty DirstateMap. Only used for a new dirstate.
84 @staticmethod
90 @staticmethod
85 def new_empty() -> PyResult<PyObject> {
91 def new_empty() -> PyResult<PyObject> {
86 let map = OwningDirstateMap::new_empty(vec![]);
92 let map = OwningDirstateMap::new_empty(vec![]);
87 let map = Self::create_instance(py, map)?;
93 let map = Self::create_instance(py, map)?;
88 Ok(map.into_object())
94 Ok(map.into_object())
89 }
95 }
90
96
91 def clear(&self) -> PyResult<PyObject> {
97 def clear(&self) -> PyResult<PyObject> {
92 self.inner(py).borrow_mut().clear();
98 self.inner(py).borrow_mut().clear();
93 Ok(py.None())
99 Ok(py.None())
94 }
100 }
95
101
96 def get(
102 def get(
97 &self,
103 &self,
98 key: PyObject,
104 key: PyObject,
99 default: Option<PyObject> = None
105 default: Option<PyObject> = None
100 ) -> PyResult<Option<PyObject>> {
106 ) -> PyResult<Option<PyObject>> {
101 let key = key.extract::<PyBytes>(py)?;
107 let key = key.extract::<PyBytes>(py)?;
102 match self
108 match self
103 .inner(py)
109 .inner(py)
104 .borrow()
110 .borrow()
105 .get(HgPath::new(key.data(py)))
111 .get(HgPath::new(key.data(py)))
106 .map_err(|e| v2_error(py, e))?
112 .map_err(|e| v2_error(py, e))?
107 {
113 {
108 Some(entry) => {
114 Some(entry) => {
109 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
115 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
110 },
116 },
111 None => Ok(default)
117 None => Ok(default)
112 }
118 }
113 }
119 }
114
120
115 def set_tracked(&self, f: PyObject) -> PyResult<PyBool> {
121 def set_tracked(&self, f: PyObject) -> PyResult<PyBool> {
116 let bytes = f.extract::<PyBytes>(py)?;
122 let bytes = f.extract::<PyBytes>(py)?;
117 let path = HgPath::new(bytes.data(py));
123 let path = HgPath::new(bytes.data(py));
118 let res = self.inner(py).borrow_mut().set_tracked(path);
124 let res = self.inner(py).borrow_mut().set_tracked(path);
119 let was_tracked = res.or_else(|_| {
125 let was_tracked = res.or_else(|_| {
120 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
126 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
121 })?;
127 })?;
122 Ok(was_tracked.to_py_object(py))
128 Ok(was_tracked.to_py_object(py))
123 }
129 }
124
130
125 def set_untracked(&self, f: PyObject) -> PyResult<PyBool> {
131 def set_untracked(&self, f: PyObject) -> PyResult<PyBool> {
126 let bytes = f.extract::<PyBytes>(py)?;
132 let bytes = f.extract::<PyBytes>(py)?;
127 let path = HgPath::new(bytes.data(py));
133 let path = HgPath::new(bytes.data(py));
128 let res = self.inner(py).borrow_mut().set_untracked(path);
134 let res = self.inner(py).borrow_mut().set_untracked(path);
129 let was_tracked = res.or_else(|_| {
135 let was_tracked = res.or_else(|_| {
130 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
136 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
131 })?;
137 })?;
132 Ok(was_tracked.to_py_object(py))
138 Ok(was_tracked.to_py_object(py))
133 }
139 }
134
140
135 def set_clean(
141 def set_clean(
136 &self,
142 &self,
137 f: PyObject,
143 f: PyObject,
138 mode: u32,
144 mode: u32,
139 size: u32,
145 size: u32,
140 mtime: (i64, u32, bool)
146 mtime: (i64, u32, bool)
141 ) -> PyResult<PyNone> {
147 ) -> PyResult<PyNone> {
142 let (mtime_s, mtime_ns, second_ambiguous) = mtime;
148 let (mtime_s, mtime_ns, second_ambiguous) = mtime;
143 let timestamp = TruncatedTimestamp::new_truncate(
149 let timestamp = TruncatedTimestamp::new_truncate(
144 mtime_s, mtime_ns, second_ambiguous
150 mtime_s, mtime_ns, second_ambiguous
145 );
151 );
146 let bytes = f.extract::<PyBytes>(py)?;
152 let bytes = f.extract::<PyBytes>(py)?;
147 let path = HgPath::new(bytes.data(py));
153 let path = HgPath::new(bytes.data(py));
148 let res = self.inner(py).borrow_mut().set_clean(
154 let res = self.inner(py).borrow_mut().set_clean(
149 path, mode, size, timestamp,
155 path, mode, size, timestamp,
150 );
156 );
151 res.or_else(|_| {
157 res.or_else(|_| {
152 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
158 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
153 })?;
159 })?;
154 Ok(PyNone)
160 Ok(PyNone)
155 }
161 }
156
162
157 def set_possibly_dirty(&self, f: PyObject) -> PyResult<PyNone> {
163 def set_possibly_dirty(&self, f: PyObject) -> PyResult<PyNone> {
158 let bytes = f.extract::<PyBytes>(py)?;
164 let bytes = f.extract::<PyBytes>(py)?;
159 let path = HgPath::new(bytes.data(py));
165 let path = HgPath::new(bytes.data(py));
160 let res = self.inner(py).borrow_mut().set_possibly_dirty(path);
166 let res = self.inner(py).borrow_mut().set_possibly_dirty(path);
161 res.or_else(|_| {
167 res.or_else(|_| {
162 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
168 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
163 })?;
169 })?;
164 Ok(PyNone)
170 Ok(PyNone)
165 }
171 }
166
172
167 def reset_state(
173 def reset_state(
168 &self,
174 &self,
169 f: PyObject,
175 f: PyObject,
170 wc_tracked: bool,
176 wc_tracked: bool,
171 p1_tracked: bool,
177 p1_tracked: bool,
172 p2_info: bool,
178 p2_info: bool,
173 has_meaningful_mtime: bool,
179 has_meaningful_mtime: bool,
174 parentfiledata: Option<(u32, u32, Option<(i64, u32, bool)>)>,
180 parentfiledata: Option<(u32, u32, Option<(i64, u32, bool)>)>,
175 ) -> PyResult<PyNone> {
181 ) -> PyResult<PyNone> {
176 let mut has_meaningful_mtime = has_meaningful_mtime;
182 let mut has_meaningful_mtime = has_meaningful_mtime;
177 let parent_file_data = match parentfiledata {
183 let parent_file_data = match parentfiledata {
178 None => {
184 None => {
179 has_meaningful_mtime = false;
185 has_meaningful_mtime = false;
180 None
186 None
181 },
187 },
182 Some(data) => {
188 Some(data) => {
183 let (mode, size, mtime_info) = data;
189 let (mode, size, mtime_info) = data;
184 let mtime = if let Some(mtime_info) = mtime_info {
190 let mtime = if let Some(mtime_info) = mtime_info {
185 let (mtime_s, mtime_ns, second_ambiguous) = mtime_info;
191 let (mtime_s, mtime_ns, second_ambiguous) = mtime_info;
186 let timestamp = TruncatedTimestamp::new_truncate(
192 let timestamp = TruncatedTimestamp::new_truncate(
187 mtime_s, mtime_ns, second_ambiguous
193 mtime_s, mtime_ns, second_ambiguous
188 );
194 );
189 Some(timestamp)
195 Some(timestamp)
190 } else {
196 } else {
191 has_meaningful_mtime = false;
197 has_meaningful_mtime = false;
192 None
198 None
193 };
199 };
194 Some(ParentFileData {
200 Some(ParentFileData {
195 mode_size: Some((mode, size)),
201 mode_size: Some((mode, size)),
196 mtime,
202 mtime,
197 })
203 })
198 }
204 }
199 };
205 };
200 let bytes = f.extract::<PyBytes>(py)?;
206 let bytes = f.extract::<PyBytes>(py)?;
201 let path = HgPath::new(bytes.data(py));
207 let path = HgPath::new(bytes.data(py));
202 let res = self.inner(py).borrow_mut().reset_state(
208 let res = self.inner(py).borrow_mut().reset_state(
203 path,
209 path,
204 wc_tracked,
210 wc_tracked,
205 p1_tracked,
211 p1_tracked,
206 p2_info,
212 p2_info,
207 has_meaningful_mtime,
213 has_meaningful_mtime,
208 parent_file_data,
214 parent_file_data,
209 );
215 );
210 res.or_else(|_| {
216 res.or_else(|_| {
211 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
217 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
212 })?;
218 })?;
213 Ok(PyNone)
219 Ok(PyNone)
214 }
220 }
215
221
216 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
222 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
217 let d = d.extract::<PyBytes>(py)?;
223 let d = d.extract::<PyBytes>(py)?;
218 Ok(self.inner(py).borrow_mut()
224 Ok(self.inner(py).borrow_mut()
219 .has_tracked_dir(HgPath::new(d.data(py)))
225 .has_tracked_dir(HgPath::new(d.data(py)))
220 .map_err(|e| {
226 .map_err(|e| {
221 PyErr::new::<exc::ValueError, _>(py, e.to_string())
227 PyErr::new::<exc::ValueError, _>(py, e.to_string())
222 })?
228 })?
223 .to_py_object(py))
229 .to_py_object(py))
224 }
230 }
225
231
226 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
232 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
227 let d = d.extract::<PyBytes>(py)?;
233 let d = d.extract::<PyBytes>(py)?;
228 Ok(self.inner(py).borrow_mut()
234 Ok(self.inner(py).borrow_mut()
229 .has_dir(HgPath::new(d.data(py)))
235 .has_dir(HgPath::new(d.data(py)))
230 .map_err(|e| {
236 .map_err(|e| {
231 PyErr::new::<exc::ValueError, _>(py, e.to_string())
237 PyErr::new::<exc::ValueError, _>(py, e.to_string())
232 })?
238 })?
233 .to_py_object(py))
239 .to_py_object(py))
234 }
240 }
235
241
236 def write_v1(
242 def write_v1(
237 &self,
243 &self,
238 p1: PyObject,
244 p1: PyObject,
239 p2: PyObject,
245 p2: PyObject,
240 ) -> PyResult<PyBytes> {
246 ) -> PyResult<PyBytes> {
241 let inner = self.inner(py).borrow();
247 let inner = self.inner(py).borrow();
242 let parents = DirstateParents {
248 let parents = DirstateParents {
243 p1: extract_node_id(py, &p1)?,
249 p1: extract_node_id(py, &p1)?,
244 p2: extract_node_id(py, &p2)?,
250 p2: extract_node_id(py, &p2)?,
245 };
251 };
246 let result = inner.pack_v1(parents);
252 let result = inner.pack_v1(parents);
247 match result {
253 match result {
248 Ok(packed) => Ok(PyBytes::new(py, &packed)),
254 Ok(packed) => Ok(PyBytes::new(py, &packed)),
249 Err(_) => Err(PyErr::new::<exc::OSError, _>(
255 Err(_) => Err(PyErr::new::<exc::OSError, _>(
250 py,
256 py,
251 "Dirstate error".to_string(),
257 "Dirstate error".to_string(),
252 )),
258 )),
253 }
259 }
254 }
260 }
255
261
256 /// Returns new data together with whether that data should be appended to
262 /// Returns new data together with whether that data should be appended to
257 /// the existing data file whose content is at `self.on_disk` (True),
263 /// the existing data file whose content is at `self.on_disk` (True),
258 /// instead of written to a new data file (False).
264 /// instead of written to a new data file (False).
259 def write_v2(
265 def write_v2(
260 &self,
266 &self,
261 write_mode: usize,
267 write_mode: usize,
262 ) -> PyResult<PyObject> {
268 ) -> PyResult<PyObject> {
263 let inner = self.inner(py).borrow();
269 let inner = self.inner(py).borrow();
264 let rust_write_mode = match write_mode {
270 let rust_write_mode = match write_mode {
265 0 => DirstateMapWriteMode::Auto,
271 0 => DirstateMapWriteMode::Auto,
266 1 => DirstateMapWriteMode::ForceNewDataFile,
272 1 => DirstateMapWriteMode::ForceNewDataFile,
267 2 => DirstateMapWriteMode::ForceAppend,
273 2 => DirstateMapWriteMode::ForceAppend,
268 _ => DirstateMapWriteMode::Auto, // XXX should we error out?
274 _ => DirstateMapWriteMode::Auto, // XXX should we error out?
269 };
275 };
270 let result = inner.pack_v2(rust_write_mode);
276 let result = inner.pack_v2(rust_write_mode);
271 match result {
277 match result {
272 Ok((packed, tree_metadata, append, _old_data_size)) => {
278 Ok((packed, tree_metadata, append, _old_data_size)) => {
273 let packed = PyBytes::new(py, &packed);
279 let packed = PyBytes::new(py, &packed);
274 let tree_metadata = PyBytes::new(py, tree_metadata.as_bytes());
280 let tree_metadata = PyBytes::new(py, tree_metadata.as_bytes());
275 let tuple = (packed, tree_metadata, append);
281 let tuple = (packed, tree_metadata, append);
276 Ok(tuple.to_py_object(py).into_object())
282 Ok(tuple.to_py_object(py).into_object())
277 },
283 },
278 Err(_) => Err(PyErr::new::<exc::OSError, _>(
284 Err(_) => Err(PyErr::new::<exc::OSError, _>(
279 py,
285 py,
280 "Dirstate error".to_string(),
286 "Dirstate error".to_string(),
281 )),
287 )),
282 }
288 }
283 }
289 }
284
290
285 def filefoldmapasdict(&self) -> PyResult<PyDict> {
291 def filefoldmapasdict(&self) -> PyResult<PyDict> {
286 let dict = PyDict::new(py);
292 let dict = PyDict::new(py);
287 for item in self.inner(py).borrow_mut().iter() {
293 for item in self.inner(py).borrow_mut().iter() {
288 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
294 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
289 if !entry.removed() {
295 if !entry.removed() {
290 let key = normalize_case(path);
296 let key = normalize_case(path);
291 let value = path;
297 let value = path;
292 dict.set_item(
298 dict.set_item(
293 py,
299 py,
294 PyBytes::new(py, key.as_bytes()).into_object(),
300 PyBytes::new(py, key.as_bytes()).into_object(),
295 PyBytes::new(py, value.as_bytes()).into_object(),
301 PyBytes::new(py, value.as_bytes()).into_object(),
296 )?;
302 )?;
297 }
303 }
298 }
304 }
299 Ok(dict)
305 Ok(dict)
300 }
306 }
301
307
302 def __len__(&self) -> PyResult<usize> {
308 def __len__(&self) -> PyResult<usize> {
303 Ok(self.inner(py).borrow().len())
309 Ok(self.inner(py).borrow().len())
304 }
310 }
305
311
306 def __contains__(&self, key: PyObject) -> PyResult<bool> {
312 def __contains__(&self, key: PyObject) -> PyResult<bool> {
307 let key = key.extract::<PyBytes>(py)?;
313 let key = key.extract::<PyBytes>(py)?;
308 self.inner(py)
314 self.inner(py)
309 .borrow()
315 .borrow()
310 .contains_key(HgPath::new(key.data(py)))
316 .contains_key(HgPath::new(key.data(py)))
311 .map_err(|e| v2_error(py, e))
317 .map_err(|e| v2_error(py, e))
312 }
318 }
313
319
314 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
320 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
315 let key = key.extract::<PyBytes>(py)?;
321 let key = key.extract::<PyBytes>(py)?;
316 let key = HgPath::new(key.data(py));
322 let key = HgPath::new(key.data(py));
317 match self
323 match self
318 .inner(py)
324 .inner(py)
319 .borrow()
325 .borrow()
320 .get(key)
326 .get(key)
321 .map_err(|e| v2_error(py, e))?
327 .map_err(|e| v2_error(py, e))?
322 {
328 {
323 Some(entry) => {
329 Some(entry) => {
324 Ok(DirstateItem::new_as_pyobject(py, entry)?)
330 Ok(DirstateItem::new_as_pyobject(py, entry)?)
325 },
331 },
326 None => Err(PyErr::new::<exc::KeyError, _>(
332 None => Err(PyErr::new::<exc::KeyError, _>(
327 py,
333 py,
328 String::from_utf8_lossy(key.as_bytes()),
334 String::from_utf8_lossy(key.as_bytes()),
329 )),
335 )),
330 }
336 }
331 }
337 }
332
338
333 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
339 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
334 let leaked_ref = self.inner(py).leak_immutable();
340 let leaked_ref = self.inner(py).leak_immutable();
335 DirstateMapKeysIterator::from_inner(
341 DirstateMapKeysIterator::from_inner(
336 py,
342 py,
337 unsafe { leaked_ref.map(py, |o| o.iter()) },
343 unsafe { leaked_ref.map(py, |o| o.iter()) },
338 )
344 )
339 }
345 }
340
346
341 def items(&self) -> PyResult<DirstateMapItemsIterator> {
347 def items(&self) -> PyResult<DirstateMapItemsIterator> {
342 let leaked_ref = self.inner(py).leak_immutable();
348 let leaked_ref = self.inner(py).leak_immutable();
343 DirstateMapItemsIterator::from_inner(
349 DirstateMapItemsIterator::from_inner(
344 py,
350 py,
345 unsafe { leaked_ref.map(py, |o| o.iter()) },
351 unsafe { leaked_ref.map(py, |o| o.iter()) },
346 )
352 )
347 }
353 }
348
354
349 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
355 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
350 let leaked_ref = self.inner(py).leak_immutable();
356 let leaked_ref = self.inner(py).leak_immutable();
351 DirstateMapKeysIterator::from_inner(
357 DirstateMapKeysIterator::from_inner(
352 py,
358 py,
353 unsafe { leaked_ref.map(py, |o| o.iter()) },
359 unsafe { leaked_ref.map(py, |o| o.iter()) },
354 )
360 )
355 }
361 }
356
362
357 // TODO all copymap* methods, see docstring above
363 // TODO all copymap* methods, see docstring above
358 def copymapcopy(&self) -> PyResult<PyDict> {
364 def copymapcopy(&self) -> PyResult<PyDict> {
359 let dict = PyDict::new(py);
365 let dict = PyDict::new(py);
360 for item in self.inner(py).borrow().copy_map_iter() {
366 for item in self.inner(py).borrow().copy_map_iter() {
361 let (key, value) = item.map_err(|e| v2_error(py, e))?;
367 let (key, value) = item.map_err(|e| v2_error(py, e))?;
362 dict.set_item(
368 dict.set_item(
363 py,
369 py,
364 PyBytes::new(py, key.as_bytes()),
370 PyBytes::new(py, key.as_bytes()),
365 PyBytes::new(py, value.as_bytes()),
371 PyBytes::new(py, value.as_bytes()),
366 )?;
372 )?;
367 }
373 }
368 Ok(dict)
374 Ok(dict)
369 }
375 }
370
376
371 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
377 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
372 let key = key.extract::<PyBytes>(py)?;
378 let key = key.extract::<PyBytes>(py)?;
373 match self
379 match self
374 .inner(py)
380 .inner(py)
375 .borrow()
381 .borrow()
376 .copy_map_get(HgPath::new(key.data(py)))
382 .copy_map_get(HgPath::new(key.data(py)))
377 .map_err(|e| v2_error(py, e))?
383 .map_err(|e| v2_error(py, e))?
378 {
384 {
379 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
385 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
380 None => Err(PyErr::new::<exc::KeyError, _>(
386 None => Err(PyErr::new::<exc::KeyError, _>(
381 py,
387 py,
382 String::from_utf8_lossy(key.data(py)),
388 String::from_utf8_lossy(key.data(py)),
383 )),
389 )),
384 }
390 }
385 }
391 }
386 def copymap(&self) -> PyResult<CopyMap> {
392 def copymap(&self) -> PyResult<CopyMap> {
387 CopyMap::from_inner(py, self.clone_ref(py))
393 CopyMap::from_inner(py, self.clone_ref(py))
388 }
394 }
389
395
390 def copymaplen(&self) -> PyResult<usize> {
396 def copymaplen(&self) -> PyResult<usize> {
391 Ok(self.inner(py).borrow().copy_map_len())
397 Ok(self.inner(py).borrow().copy_map_len())
392 }
398 }
393 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
399 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
394 let key = key.extract::<PyBytes>(py)?;
400 let key = key.extract::<PyBytes>(py)?;
395 self.inner(py)
401 self.inner(py)
396 .borrow()
402 .borrow()
397 .copy_map_contains_key(HgPath::new(key.data(py)))
403 .copy_map_contains_key(HgPath::new(key.data(py)))
398 .map_err(|e| v2_error(py, e))
404 .map_err(|e| v2_error(py, e))
399 }
405 }
400 def copymapget(
406 def copymapget(
401 &self,
407 &self,
402 key: PyObject,
408 key: PyObject,
403 default: Option<PyObject>
409 default: Option<PyObject>
404 ) -> PyResult<Option<PyObject>> {
410 ) -> PyResult<Option<PyObject>> {
405 let key = key.extract::<PyBytes>(py)?;
411 let key = key.extract::<PyBytes>(py)?;
406 match self
412 match self
407 .inner(py)
413 .inner(py)
408 .borrow()
414 .borrow()
409 .copy_map_get(HgPath::new(key.data(py)))
415 .copy_map_get(HgPath::new(key.data(py)))
410 .map_err(|e| v2_error(py, e))?
416 .map_err(|e| v2_error(py, e))?
411 {
417 {
412 Some(copy) => Ok(Some(
418 Some(copy) => Ok(Some(
413 PyBytes::new(py, copy.as_bytes()).into_object(),
419 PyBytes::new(py, copy.as_bytes()).into_object(),
414 )),
420 )),
415 None => Ok(default),
421 None => Ok(default),
416 }
422 }
417 }
423 }
418 def copymapsetitem(
424 def copymapsetitem(
419 &self,
425 &self,
420 key: PyObject,
426 key: PyObject,
421 value: PyObject
427 value: PyObject
422 ) -> PyResult<PyObject> {
428 ) -> PyResult<PyObject> {
423 let key = key.extract::<PyBytes>(py)?;
429 let key = key.extract::<PyBytes>(py)?;
424 let value = value.extract::<PyBytes>(py)?;
430 let value = value.extract::<PyBytes>(py)?;
425 self.inner(py)
431 self.inner(py)
426 .borrow_mut()
432 .borrow_mut()
427 .copy_map_insert(
433 .copy_map_insert(
428 HgPath::new(key.data(py)),
434 HgPath::new(key.data(py)),
429 HgPath::new(value.data(py)),
435 HgPath::new(value.data(py)),
430 )
436 )
431 .map_err(|e| v2_error(py, e))?;
437 .map_err(|e| v2_error(py, e))?;
432 Ok(py.None())
438 Ok(py.None())
433 }
439 }
434 def copymappop(
440 def copymappop(
435 &self,
441 &self,
436 key: PyObject,
442 key: PyObject,
437 default: Option<PyObject>
443 default: Option<PyObject>
438 ) -> PyResult<Option<PyObject>> {
444 ) -> PyResult<Option<PyObject>> {
439 let key = key.extract::<PyBytes>(py)?;
445 let key = key.extract::<PyBytes>(py)?;
440 match self
446 match self
441 .inner(py)
447 .inner(py)
442 .borrow_mut()
448 .borrow_mut()
443 .copy_map_remove(HgPath::new(key.data(py)))
449 .copy_map_remove(HgPath::new(key.data(py)))
444 .map_err(|e| v2_error(py, e))?
450 .map_err(|e| v2_error(py, e))?
445 {
451 {
446 Some(copy) => Ok(Some(
452 Some(copy) => Ok(Some(
447 PyBytes::new(py, copy.as_bytes()).into_object(),
453 PyBytes::new(py, copy.as_bytes()).into_object(),
448 )),
454 )),
449 None => Ok(default),
455 None => Ok(default),
450 }
456 }
451 }
457 }
452
458
453 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
459 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
454 let leaked_ref = self.inner(py).leak_immutable();
460 let leaked_ref = self.inner(py).leak_immutable();
455 CopyMapKeysIterator::from_inner(
461 CopyMapKeysIterator::from_inner(
456 py,
462 py,
457 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
463 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
458 )
464 )
459 }
465 }
460
466
461 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
467 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
462 let leaked_ref = self.inner(py).leak_immutable();
468 let leaked_ref = self.inner(py).leak_immutable();
463 CopyMapItemsIterator::from_inner(
469 CopyMapItemsIterator::from_inner(
464 py,
470 py,
465 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
471 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
466 )
472 )
467 }
473 }
468
474
469 def tracked_dirs(&self) -> PyResult<PyList> {
475 def tracked_dirs(&self) -> PyResult<PyList> {
470 let dirs = PyList::new(py, &[]);
476 let dirs = PyList::new(py, &[]);
471 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
477 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
472 .map_err(|e |dirstate_error(py, e))?
478 .map_err(|e |dirstate_error(py, e))?
473 {
479 {
474 let path = path.map_err(|e| v2_error(py, e))?;
480 let path = path.map_err(|e| v2_error(py, e))?;
475 let path = PyBytes::new(py, path.as_bytes());
481 let path = PyBytes::new(py, path.as_bytes());
476 dirs.append(py, path.into_object())
482 dirs.append(py, path.into_object())
477 }
483 }
478 Ok(dirs)
484 Ok(dirs)
479 }
485 }
480
486
481 def setparents_fixup(&self) -> PyResult<PyDict> {
487 def setparents_fixup(&self) -> PyResult<PyDict> {
482 let dict = PyDict::new(py);
488 let dict = PyDict::new(py);
483 let copies = self.inner(py).borrow_mut().setparents_fixup();
489 let copies = self.inner(py).borrow_mut().setparents_fixup();
484 for (key, value) in copies.map_err(|e| v2_error(py, e))? {
490 for (key, value) in copies.map_err(|e| v2_error(py, e))? {
485 dict.set_item(
491 dict.set_item(
486 py,
492 py,
487 PyBytes::new(py, key.as_bytes()),
493 PyBytes::new(py, key.as_bytes()),
488 PyBytes::new(py, value.as_bytes()),
494 PyBytes::new(py, value.as_bytes()),
489 )?;
495 )?;
490 }
496 }
491 Ok(dict)
497 Ok(dict)
492 }
498 }
493
499
494 def debug_iter(&self, all: bool) -> PyResult<PyList> {
500 def debug_iter(&self, all: bool) -> PyResult<PyList> {
495 let dirs = PyList::new(py, &[]);
501 let dirs = PyList::new(py, &[]);
496 for item in self.inner(py).borrow().debug_iter(all) {
502 for item in self.inner(py).borrow().debug_iter(all) {
497 let (path, (state, mode, size, mtime)) =
503 let (path, (state, mode, size, mtime)) =
498 item.map_err(|e| v2_error(py, e))?;
504 item.map_err(|e| v2_error(py, e))?;
499 let path = PyBytes::new(py, path.as_bytes());
505 let path = PyBytes::new(py, path.as_bytes());
500 let item = (path, state, mode, size, mtime);
506 let item = (path, state, mode, size, mtime);
501 dirs.append(py, item.to_py_object(py).into_object())
507 dirs.append(py, item.to_py_object(py).into_object())
502 }
508 }
503 Ok(dirs)
509 Ok(dirs)
504 }
510 }
505 });
511 });
506
512
507 impl DirstateMap {
513 impl DirstateMap {
508 pub fn get_inner_mut<'a>(
514 pub fn get_inner_mut<'a>(
509 &'a self,
515 &'a self,
510 py: Python<'a>,
516 py: Python<'a>,
511 ) -> RefMut<'a, OwningDirstateMap> {
517 ) -> RefMut<'a, OwningDirstateMap> {
512 self.inner(py).borrow_mut()
518 self.inner(py).borrow_mut()
513 }
519 }
514 fn translate_key(
520 fn translate_key(
515 py: Python,
521 py: Python,
516 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
522 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
517 ) -> PyResult<Option<PyBytes>> {
523 ) -> PyResult<Option<PyBytes>> {
518 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
524 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
519 Ok(Some(PyBytes::new(py, f.as_bytes())))
525 Ok(Some(PyBytes::new(py, f.as_bytes())))
520 }
526 }
521 fn translate_key_value(
527 fn translate_key_value(
522 py: Python,
528 py: Python,
523 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
529 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
524 ) -> PyResult<Option<(PyBytes, PyObject)>> {
530 ) -> PyResult<Option<(PyBytes, PyObject)>> {
525 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
531 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
526 Ok(Some((
532 Ok(Some((
527 PyBytes::new(py, f.as_bytes()),
533 PyBytes::new(py, f.as_bytes()),
528 DirstateItem::new_as_pyobject(py, entry)?,
534 DirstateItem::new_as_pyobject(py, entry)?,
529 )))
535 )))
530 }
536 }
531 }
537 }
532
538
533 py_shared_iterator!(
539 py_shared_iterator!(
534 DirstateMapKeysIterator,
540 DirstateMapKeysIterator,
535 UnsafePyLeaked<StateMapIter<'static>>,
541 UnsafePyLeaked<StateMapIter<'static>>,
536 DirstateMap::translate_key,
542 DirstateMap::translate_key,
537 Option<PyBytes>
543 Option<PyBytes>
538 );
544 );
539
545
540 py_shared_iterator!(
546 py_shared_iterator!(
541 DirstateMapItemsIterator,
547 DirstateMapItemsIterator,
542 UnsafePyLeaked<StateMapIter<'static>>,
548 UnsafePyLeaked<StateMapIter<'static>>,
543 DirstateMap::translate_key_value,
549 DirstateMap::translate_key_value,
544 Option<(PyBytes, PyObject)>
550 Option<(PyBytes, PyObject)>
545 );
551 );
546
552
547 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
553 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
548 let bytes = obj.extract::<PyBytes>(py)?;
554 let bytes = obj.extract::<PyBytes>(py)?;
549 match bytes.data(py).try_into() {
555 match bytes.data(py).try_into() {
550 Ok(s) => Ok(s),
556 Ok(s) => Ok(s),
551 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
557 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
552 }
558 }
553 }
559 }
554
560
555 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
561 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
556 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
562 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
557 }
563 }
558
564
559 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
565 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
560 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
566 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
561 }
567 }
@@ -1,482 +1,460 b''
1 =====================================================================
1 =====================================================================
2 Check potential race conditions between a status and other operations
2 Check potential race conditions between a status 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
6
7 The `hg status` command can run without the wlock, however it might end up
7 The `hg status` command can run without the wlock, however it might end up
8 having to update the on-disk dirstate files, for example to mark ambiguous
8 having to update the on-disk dirstate files, for example to mark ambiguous
9 files as clean, or to update directory caches information with dirstate-v2.
9 files as clean, or to update directory caches information with dirstate-v2.
10
10
11
11
12 If another process updates the dirstate in the meantime we might run into
12 If another process updates the dirstate in the meantime we might run into
13 trouble. Especially, commands doing semantic changes like `hg add` or
13 trouble. Especially, commands doing semantic changes like `hg add` or
14 `hg commit` should not see their update erased by a concurrent status.
14 `hg commit` should not see their update erased by a concurrent status.
15
15
16 Unlike commands like `add` or `commit`, `status` only writes the dirstate
16 Unlike commands like `add` or `commit`, `status` only writes the dirstate
17 to update caches, no actual information is lost if we fail to write to disk.
17 to update caches, no actual information is lost if we fail to write to disk.
18
18
19
19
20 This test file is meant to test various cases where such parallel operations
20 This test file is meant to test various cases where such parallel operations
21 between a status with reasons to update the dirstate and another semantic
21 between a status with reasons to update the dirstate and another semantic
22 changes happen.
22 changes happen.
23
23
24
24
25 Setup
25 Setup
26 =====
26 =====
27
27
28 $ cat >> $HGRCPATH << EOF
28 $ cat >> $HGRCPATH << EOF
29 > [storage]
29 > [storage]
30 > dirstate-v2.slow-path=allow
30 > dirstate-v2.slow-path=allow
31 > EOF
31 > EOF
32
32
33 #if no-dirstate-v1
33 #if no-dirstate-v1
34 $ cat >> $HGRCPATH << EOF
34 $ cat >> $HGRCPATH << EOF
35 > [format]
35 > [format]
36 > use-dirstate-v2=yes
36 > use-dirstate-v2=yes
37 > EOF
37 > EOF
38 #else
38 #else
39 $ cat >> $HGRCPATH << EOF
39 $ cat >> $HGRCPATH << EOF
40 > [format]
40 > [format]
41 > use-dirstate-v2=no
41 > use-dirstate-v2=no
42 > EOF
42 > EOF
43 #endif
43 #endif
44
44
45 #if dirstate-v2-rewrite
45 #if dirstate-v2-rewrite
46 $ d2args="--config devel.dirstate.v2.data_update_mode=force-new"
46 $ d2args="--config devel.dirstate.v2.data_update_mode=force-new"
47 #endif
47 #endif
48 #if dirstate-v2-append
48 #if dirstate-v2-append
49 $ d2args="--config devel.dirstate.v2.data_update_mode=force-append"
49 $ d2args="--config devel.dirstate.v2.data_update_mode=force-append"
50 #endif
50 #endif
51
51
52 $ directories="dir dir/nested dir2"
52 $ directories="dir dir/nested dir2"
53 $ first_files="dir/nested/a dir/b dir/c dir/d dir2/e f"
53 $ first_files="dir/nested/a dir/b dir/c dir/d dir2/e f"
54 $ second_files="g dir/nested/h dir/i dir/j dir2/k dir2/l dir/nested/m"
54 $ second_files="g dir/nested/h dir/i dir/j dir2/k dir2/l dir/nested/m"
55 $ extra_files="dir/n dir/o p q"
55 $ extra_files="dir/n dir/o p q"
56
56
57 $ hg init reference-repo
57 $ hg init reference-repo
58 $ cd reference-repo
58 $ cd reference-repo
59 $ mkdir -p dir/nested dir2
59 $ mkdir -p dir/nested dir2
60 $ touch -t 200001010000 $first_files $directories
60 $ touch -t 200001010000 $first_files $directories
61 $ hg commit -Aqm "recreate a bunch of files to facilitate dirstate-v2 append"
61 $ hg commit -Aqm "recreate a bunch of files to facilitate dirstate-v2 append"
62 $ touch -t 200001010010 $second_files $directories
62 $ touch -t 200001010010 $second_files $directories
63 $ hg commit -Aqm "more files to have two commits"
63 $ hg commit -Aqm "more files to have two commits"
64 $ hg log -G -v
64 $ hg log -G -v
65 @ changeset: 1:c349430a1631
65 @ changeset: 1:c349430a1631
66 | tag: tip
66 | tag: tip
67 | user: test
67 | user: test
68 | date: Thu Jan 01 00:00:00 1970 +0000
68 | date: Thu Jan 01 00:00:00 1970 +0000
69 | files: dir/i dir/j dir/nested/h dir/nested/m dir2/k dir2/l g
69 | files: dir/i dir/j dir/nested/h dir/nested/m dir2/k dir2/l g
70 | description:
70 | description:
71 | more files to have two commits
71 | more files to have two commits
72 |
72 |
73 |
73 |
74 o changeset: 0:4f23db756b09
74 o changeset: 0:4f23db756b09
75 user: test
75 user: test
76 date: Thu Jan 01 00:00:00 1970 +0000
76 date: Thu Jan 01 00:00:00 1970 +0000
77 files: dir/b dir/c dir/d dir/nested/a dir2/e f
77 files: dir/b dir/c dir/d dir/nested/a dir2/e f
78 description:
78 description:
79 recreate a bunch of files to facilitate dirstate-v2 append
79 recreate a bunch of files to facilitate dirstate-v2 append
80
80
81
81
82 $ hg manifest
82 $ hg manifest
83 dir/b
83 dir/b
84 dir/c
84 dir/c
85 dir/d
85 dir/d
86 dir/i
86 dir/i
87 dir/j
87 dir/j
88 dir/nested/a
88 dir/nested/a
89 dir/nested/h
89 dir/nested/h
90 dir/nested/m
90 dir/nested/m
91 dir2/e
91 dir2/e
92 dir2/k
92 dir2/k
93 dir2/l
93 dir2/l
94 f
94 f
95 g
95 g
96
96
97 Add some unknown files and refresh the dirstate
97 Add some unknown files and refresh the dirstate
98
98
99 $ touch -t 200001010020 $extra_files
99 $ touch -t 200001010020 $extra_files
100 $ hg add dir/o
100 $ hg add dir/o
101 $ hg remove dir/nested/m
101 $ hg remove dir/nested/m
102
102
103 $ hg st --config devel.dirstate.v2.data_update_mode=force-new
103 $ hg st --config devel.dirstate.v2.data_update_mode=force-new
104 A dir/o
104 A dir/o
105 R dir/nested/m
105 R dir/nested/m
106 ? dir/n
106 ? dir/n
107 ? p
107 ? p
108 ? q
108 ? q
109 $ hg debugstate
109 $ hg debugstate
110 n 644 0 2000-01-01 00:00:00 dir/b
110 n 644 0 2000-01-01 00:00:00 dir/b
111 n 644 0 2000-01-01 00:00:00 dir/c
111 n 644 0 2000-01-01 00:00:00 dir/c
112 n 644 0 2000-01-01 00:00:00 dir/d
112 n 644 0 2000-01-01 00:00:00 dir/d
113 n 644 0 2000-01-01 00:10:00 dir/i
113 n 644 0 2000-01-01 00:10:00 dir/i
114 n 644 0 2000-01-01 00:10:00 dir/j
114 n 644 0 2000-01-01 00:10:00 dir/j
115 n 644 0 2000-01-01 00:00:00 dir/nested/a
115 n 644 0 2000-01-01 00:00:00 dir/nested/a
116 n 644 0 2000-01-01 00:10:00 dir/nested/h
116 n 644 0 2000-01-01 00:10:00 dir/nested/h
117 r ?????????????????????????????????? dir/nested/m (glob)
117 r ?????????????????????????????????? dir/nested/m (glob)
118 a ?????????????????????????????????? dir/o (glob)
118 a ?????????????????????????????????? dir/o (glob)
119 n 644 0 2000-01-01 00:00:00 dir2/e
119 n 644 0 2000-01-01 00:00:00 dir2/e
120 n 644 0 2000-01-01 00:10:00 dir2/k
120 n 644 0 2000-01-01 00:10:00 dir2/k
121 n 644 0 2000-01-01 00:10:00 dir2/l
121 n 644 0 2000-01-01 00:10:00 dir2/l
122 n 644 0 2000-01-01 00:00:00 f
122 n 644 0 2000-01-01 00:00:00 f
123 n 644 0 2000-01-01 00:10:00 g
123 n 644 0 2000-01-01 00:10:00 g
124 $ hg debugstate > ../reference
124 $ hg debugstate > ../reference
125 $ cd ..
125 $ cd ..
126
126
127 Explain / verify the test principles
127 Explain / verify the test principles
128 ------------------------------------
128 ------------------------------------
129
129
130 First, we can properly copy the reference
130 First, we can properly copy the reference
131
131
132 $ cp -a reference-repo sanity-check
132 $ cp -a reference-repo sanity-check
133 $ cd sanity-check
133 $ cd sanity-check
134 $ hg debugstate
134 $ hg debugstate
135 n 644 0 2000-01-01 00:00:00 dir/b
135 n 644 0 2000-01-01 00:00:00 dir/b
136 n 644 0 2000-01-01 00:00:00 dir/c
136 n 644 0 2000-01-01 00:00:00 dir/c
137 n 644 0 2000-01-01 00:00:00 dir/d
137 n 644 0 2000-01-01 00:00:00 dir/d
138 n 644 0 2000-01-01 00:10:00 dir/i
138 n 644 0 2000-01-01 00:10:00 dir/i
139 n 644 0 2000-01-01 00:10:00 dir/j
139 n 644 0 2000-01-01 00:10:00 dir/j
140 n 644 0 2000-01-01 00:00:00 dir/nested/a
140 n 644 0 2000-01-01 00:00:00 dir/nested/a
141 n 644 0 2000-01-01 00:10:00 dir/nested/h
141 n 644 0 2000-01-01 00:10:00 dir/nested/h
142 r ?????????????????????????????????? dir/nested/m (glob)
142 r ?????????????????????????????????? dir/nested/m (glob)
143 a ?????????????????????????????????? dir/o (glob)
143 a ?????????????????????????????????? dir/o (glob)
144 n 644 0 2000-01-01 00:00:00 dir2/e
144 n 644 0 2000-01-01 00:00:00 dir2/e
145 n 644 0 2000-01-01 00:10:00 dir2/k
145 n 644 0 2000-01-01 00:10:00 dir2/k
146 n 644 0 2000-01-01 00:10:00 dir2/l
146 n 644 0 2000-01-01 00:10:00 dir2/l
147 n 644 0 2000-01-01 00:00:00 f
147 n 644 0 2000-01-01 00:00:00 f
148 n 644 0 2000-01-01 00:10:00 g
148 n 644 0 2000-01-01 00:10:00 g
149 $ hg debugstate > ../post-copy
149 $ hg debugstate > ../post-copy
150 $ diff ../reference ../post-copy
150 $ diff ../reference ../post-copy
151
151
152 And status thinks the cache is in a proper state
152 And status thinks the cache is in a proper state
153
153
154 $ hg st
154 $ hg st
155 A dir/o
155 A dir/o
156 R dir/nested/m
156 R dir/nested/m
157 ? dir/n
157 ? dir/n
158 ? p
158 ? p
159 ? q
159 ? q
160 $ hg debugstate
160 $ hg debugstate
161 n 644 0 2000-01-01 00:00:00 dir/b
161 n 644 0 2000-01-01 00:00:00 dir/b
162 n 644 0 2000-01-01 00:00:00 dir/c
162 n 644 0 2000-01-01 00:00:00 dir/c
163 n 644 0 2000-01-01 00:00:00 dir/d
163 n 644 0 2000-01-01 00:00:00 dir/d
164 n 644 0 2000-01-01 00:10:00 dir/i
164 n 644 0 2000-01-01 00:10:00 dir/i
165 n 644 0 2000-01-01 00:10:00 dir/j
165 n 644 0 2000-01-01 00:10:00 dir/j
166 n 644 0 2000-01-01 00:00:00 dir/nested/a
166 n 644 0 2000-01-01 00:00:00 dir/nested/a
167 n 644 0 2000-01-01 00:10:00 dir/nested/h
167 n 644 0 2000-01-01 00:10:00 dir/nested/h
168 r ?????????????????????????????????? dir/nested/m (glob)
168 r ?????????????????????????????????? dir/nested/m (glob)
169 a ?????????????????????????????????? dir/o (glob)
169 a ?????????????????????????????????? dir/o (glob)
170 n 644 0 2000-01-01 00:00:00 dir2/e
170 n 644 0 2000-01-01 00:00:00 dir2/e
171 n 644 0 2000-01-01 00:10:00 dir2/k
171 n 644 0 2000-01-01 00:10:00 dir2/k
172 n 644 0 2000-01-01 00:10:00 dir2/l
172 n 644 0 2000-01-01 00:10:00 dir2/l
173 n 644 0 2000-01-01 00:00:00 f
173 n 644 0 2000-01-01 00:00:00 f
174 n 644 0 2000-01-01 00:10:00 g
174 n 644 0 2000-01-01 00:10:00 g
175 $ hg debugstate > ../post-status
175 $ hg debugstate > ../post-status
176 $ diff ../reference ../post-status
176 $ diff ../reference ../post-status
177
177
178 Then we can start a status that:
178 Then we can start a status that:
179 - has some update to do (the touch call)
179 - has some update to do (the touch call)
180 - will wait AFTER running status, but before updating the cache on disk
180 - will wait AFTER running status, but before updating the cache on disk
181
181
182 $ touch -t 200001010001 dir/c
182 $ touch -t 200001010001 dir/c
183 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
183 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
184 > --config rhg.on-unsupported=abort \
184 > --config rhg.on-unsupported=abort \
185 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
185 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
186 > &
186 > &
187 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
187 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
188
188
189 We check it runs the status first by modifying a file and updating another timestamp
189 We check it runs the status first by modifying a file and updating another timestamp
190
190
191 $ touch -t 200001010003 dir/i
191 $ touch -t 200001010003 dir/i
192 $ echo babar > dir/j
192 $ echo babar > dir/j
193 $ touch $TESTTMP/status-race-lock
193 $ touch $TESTTMP/status-race-lock
194 $ wait
194 $ wait
195
195
196 The test process should have reported a status before the change we made,
196 The test process should have reported a status before the change we made,
197 and should have missed the timestamp update
197 and should have missed the timestamp update
198
198
199 $ cat $TESTTMP/status-race-lock.out
199 $ cat $TESTTMP/status-race-lock.out
200 A dir/o
200 A dir/o
201 R dir/nested/m
201 R dir/nested/m
202 ? dir/n
202 ? dir/n
203 ? p
203 ? p
204 ? q
204 ? q
205 $ cat $TESTTMP/status-race-lock.log
205 $ cat $TESTTMP/status-race-lock.log
206 $ hg debugstate | grep dir/c
206 $ hg debugstate | grep dir/c
207 n 644 0 2000-01-01 00:01:00 dir/c
207 n 644 0 2000-01-01 00:01:00 dir/c
208 $ hg debugstate | grep dir/i
208 $ hg debugstate | grep dir/i
209 n 644 0 2000-01-01 00:10:00 dir/i
209 n 644 0 2000-01-01 00:10:00 dir/i
210 $ hg debugstate | grep dir/j
210 $ hg debugstate | grep dir/j
211 n 644 0 2000-01-01 00:10:00 dir/j
211 n 644 0 2000-01-01 00:10:00 dir/j
212
212
213 final cleanup
213 final cleanup
214
214
215 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
215 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
216 $ cd ..
216 $ cd ..
217
217
218 Actual Testing
218 Actual Testing
219 ==============
219 ==============
220
220
221 Race with a `hg add`
221 Race with a `hg add`
222 -------------------
222 -------------------
223
223
224 $ cp -a reference-repo race-with-add
224 $ cp -a reference-repo race-with-add
225 $ cd race-with-add
225 $ cd race-with-add
226
226
227 spin a `hg status` with some caches to update
227 spin a `hg status` with some caches to update
228
228
229 $ touch -t 200001020001 f
229 $ touch -t 200001020001 f
230 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
230 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
231 > --config rhg.on-unsupported=abort \
231 > --config rhg.on-unsupported=abort \
232 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
232 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
233 > &
233 > &
234 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
234 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
235
235
236 Add a file
236 Add a file
237
237
238 $ hg $d2args add dir/n
238 $ hg $d2args add dir/n
239 $ touch $TESTTMP/status-race-lock
239 $ touch $TESTTMP/status-race-lock
240 $ wait
240 $ wait
241
241
242 The file should in a "added" state
242 The file should in a "added" state
243
243
244 $ hg status
244 $ hg status
245 A dir/n (no-rhg dirstate-v1 !)
245 A dir/n
246 A dir/n (no-dirstate-v1 !)
247 A dir/n (missing-correct-output rhg dirstate-v1 !)
248 A dir/o
246 A dir/o
249 R dir/nested/m
247 R dir/nested/m
250 ? dir/n (known-bad-output rhg dirstate-v1 !)
251 ? p
248 ? p
252 ? q
249 ? q
253
250
254 The status process should return a consistent result and not crash.
251 The status process should return a consistent result and not crash.
255
252
256 $ cat $TESTTMP/status-race-lock.out
253 $ cat $TESTTMP/status-race-lock.out
257 A dir/o
254 A dir/o
258 R dir/nested/m
255 R dir/nested/m
259 ? dir/n
256 ? dir/n
260 ? p
257 ? p
261 ? q
258 ? q
262 $ cat $TESTTMP/status-race-lock.log
259 $ cat $TESTTMP/status-race-lock.log
263
260
264 final cleanup
261 final cleanup
265
262
266 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
263 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
267 $ cd ..
264 $ cd ..
268
265
269 Race with a `hg commit`
266 Race with a `hg commit`
270 ----------------------
267 ----------------------
271
268
272 $ cp -a reference-repo race-with-commit
269 $ cp -a reference-repo race-with-commit
273 $ cd race-with-commit
270 $ cd race-with-commit
274
271
275 spin a `hg status` with some caches to update
272 spin a `hg status` with some caches to update
276
273
277 $ touch -t 200001020001 dir/j
274 $ touch -t 200001020001 dir/j
278 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
275 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
279 > --config rhg.on-unsupported=abort \
276 > --config rhg.on-unsupported=abort \
280 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
277 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
281 > &
278 > &
282 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
279 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
283
280
284 Add a file and force the data file rewrite
281 Add a file and force the data file rewrite
285
282
286 $ hg $d2args commit -m created-during-status dir/o
283 $ hg $d2args commit -m created-during-status dir/o
287 $ touch $TESTTMP/status-race-lock
284 $ touch $TESTTMP/status-race-lock
288 $ wait
285 $ wait
289
286
290 The parent must change and the status should be clean
287 The parent must change and the status should be clean
291
288
292 # XXX rhg misbehaves here
293 #if rhg dirstate-v1
294 $ hg summary
295 parent: 1:c349430a1631
296 more files to have two commits
297 branch: default
298 commit: 1 added, 1 removed, 3 unknown (new branch head)
299 update: 1 new changesets (update)
300 phases: 3 draft
301 $ hg status
302 A dir/o
303 R dir/nested/m
304 ? dir/n
305 ? p
306 ? q
307 #else
308 $ hg summary
289 $ hg summary
309 parent: 2:2e3b442a2fd4 tip
290 parent: 2:2e3b442a2fd4 tip
310 created-during-status
291 created-during-status
311 branch: default
292 branch: default
312 commit: 1 removed, 3 unknown
293 commit: 1 removed, 3 unknown
313 update: (current)
294 update: (current)
314 phases: 3 draft
295 phases: 3 draft
315 $ hg status
296 $ hg status
316 R dir/nested/m
297 R dir/nested/m
317 ? dir/n
298 ? dir/n
318 ? p
299 ? p
319 ? q
300 ? q
320 #endif
321
301
322 The status process should return a consistent result and not crash.
302 The status process should return a consistent result and not crash.
323
303
324 $ cat $TESTTMP/status-race-lock.out
304 $ cat $TESTTMP/status-race-lock.out
325 A dir/o
305 A dir/o
326 R dir/nested/m
306 R dir/nested/m
327 ? dir/n
307 ? dir/n
328 ? p
308 ? p
329 ? q
309 ? q
330 $ cat $TESTTMP/status-race-lock.log
310 $ cat $TESTTMP/status-race-lock.log
331
311
332 final cleanup
312 final cleanup
333
313
334 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
314 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
335 $ cd ..
315 $ cd ..
336
316
337 Race with a `hg update`
317 Race with a `hg update`
338 ----------------------
318 ----------------------
339
319
340 $ cp -a reference-repo race-with-update
320 $ cp -a reference-repo race-with-update
341 $ cd race-with-update
321 $ cd race-with-update
342
322
343 spin a `hg status` with some caches to update
323 spin a `hg status` with some caches to update
344
324
345 $ touch -t 200001020001 dir2/k
325 $ touch -t 200001020001 dir2/k
346 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
326 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
347 > --config rhg.on-unsupported=abort \
327 > --config rhg.on-unsupported=abort \
348 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
328 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
349 > &
329 > &
350 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
330 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
351
331
352 Add a file and force the data file rewrite
332 Add a file and force the data file rewrite
353
333
354 $ hg $d2args update ".~1"
334 $ hg $d2args update ".~1"
355 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
335 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
356 $ touch $TESTTMP/status-race-lock
336 $ touch $TESTTMP/status-race-lock
357 $ wait
337 $ wait
358
338
359 The parent must change and the status should be clean
339 The parent must change and the status should be clean
360
340
361 $ hg summary
341 $ hg summary
362 parent: 0:4f23db756b09
342 parent: 0:4f23db756b09
363 recreate a bunch of files to facilitate dirstate-v2 append
343 recreate a bunch of files to facilitate dirstate-v2 append
364 branch: default
344 branch: default
365 commit: 1 added, 3 unknown (new branch head)
345 commit: 1 added, 3 unknown (new branch head)
366 update: 1 new changesets (update)
346 update: 1 new changesets (update)
367 phases: 2 draft
347 phases: 2 draft
368 $ hg status
348 $ hg status
369 A dir/o
349 A dir/o
370 ? dir/n
350 ? dir/n
371 ? p
351 ? p
372 ? q
352 ? q
373
353
374 The status process should return a consistent result and not crash.
354 The status process should return a consistent result and not crash.
375
355
376 $ cat $TESTTMP/status-race-lock.out
356 $ cat $TESTTMP/status-race-lock.out
377 A dir/o
357 A dir/o
378 R dir/nested/m
358 R dir/nested/m
379 ? dir/n
359 ? dir/n
380 ? p
360 ? p
381 ? q
361 ? q
382 $ cat $TESTTMP/status-race-lock.log
362 $ cat $TESTTMP/status-race-lock.log
383
363
384 final cleanup
364 final cleanup
385
365
386 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
366 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
387 $ cd ..
367 $ cd ..
388
368
389 Race with another status
369 Race with another status
390 ------------------------
370 ------------------------
391
371
392 $ cp -a reference-repo race-with-status
372 $ cp -a reference-repo race-with-status
393 $ cd race-with-status
373 $ cd race-with-status
394
374
395 spin a `hg status` with some caches to update
375 spin a `hg status` with some caches to update
396
376
397 $ touch -t 200001010030 dir/nested/h
377 $ touch -t 200001010030 dir/nested/h
398 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
378 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
399 > --config rhg.on-unsupported=abort \
379 > --config rhg.on-unsupported=abort \
400 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
380 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
401 > &
381 > &
402 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
382 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
403
383
404 touch g
384 touch g
405
385
406 $ touch -t 200001010025 g
386 $ touch -t 200001010025 g
407 $ hg $d2args status
387 $ hg $d2args status
408 A dir/o
388 A dir/o
409 R dir/nested/m
389 R dir/nested/m
410 ? dir/n
390 ? dir/n
411 ? p
391 ? p
412 ? q
392 ? q
413 $ touch $TESTTMP/status-race-lock
393 $ touch $TESTTMP/status-race-lock
414 $ wait
394 $ wait
415
395
416 the first update should be on disk
396 the first update should be on disk
417
397
418 $ hg debugstate --all | grep "g"
398 $ hg debugstate --all | grep "g"
419 n 644 0 2000-01-01 00:10:00 g (known-bad-output rhg dirstate-v1 !)
399 n 644 0 2000-01-01 00:25:00 g
420 n 644 0 2000-01-01 00:25:00 g (rhg no-dirstate-v1 !)
421 n 644 0 2000-01-01 00:25:00 g (no-rhg !)
422
400
423 The status process should return a consistent result and not crash.
401 The status process should return a consistent result and not crash.
424
402
425 $ cat $TESTTMP/status-race-lock.out
403 $ cat $TESTTMP/status-race-lock.out
426 A dir/o
404 A dir/o
427 R dir/nested/m
405 R dir/nested/m
428 ? dir/n
406 ? dir/n
429 ? p
407 ? p
430 ? q
408 ? q
431 $ cat $TESTTMP/status-race-lock.log
409 $ cat $TESTTMP/status-race-lock.log
432
410
433 final cleanup
411 final cleanup
434
412
435 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
413 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
436 $ cd ..
414 $ cd ..
437
415
438 Race with the removal of an ambiguous file
416 Race with the removal of an ambiguous file
439 ----------------------è-------------------
417 ----------------------è-------------------
440
418
441 $ cp -a reference-repo race-with-remove
419 $ cp -a reference-repo race-with-remove
442 $ cd race-with-remove
420 $ cd race-with-remove
443
421
444 spin a `hg status` with some caches to update
422 spin a `hg status` with some caches to update
445
423
446 $ touch -t 200001010035 dir2/l
424 $ touch -t 200001010035 dir2/l
447 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
425 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
448 > --config rhg.on-unsupported=abort \
426 > --config rhg.on-unsupported=abort \
449 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
427 > --config devel.sync.status.pre-dirstate-write-file=$TESTTMP/status-race-lock \
450 > &
428 > &
451 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
429 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
452
430
453 remove that same file
431 remove that same file
454
432
455 $ hg $d2args remove dir2/l
433 $ hg $d2args remove dir2/l
456 $ touch $TESTTMP/status-race-lock
434 $ touch $TESTTMP/status-race-lock
457 $ wait
435 $ wait
458
436
459 file should be marked as removed
437 file should be marked as removed
460
438
461 $ hg status
439 $ hg status
462 A dir/o
440 A dir/o
463 R dir/nested/m
441 R dir/nested/m
464 R dir2/l
442 R dir2/l
465 ? dir/n
443 ? dir/n
466 ? p
444 ? p
467 ? q
445 ? q
468
446
469 The status process should return a consistent result and not crash.
447 The status process should return a consistent result and not crash.
470
448
471 $ cat $TESTTMP/status-race-lock.out
449 $ cat $TESTTMP/status-race-lock.out
472 A dir/o
450 A dir/o
473 R dir/nested/m
451 R dir/nested/m
474 ? dir/n
452 ? dir/n
475 ? p
453 ? p
476 ? q
454 ? q
477 $ cat $TESTTMP/status-race-lock.log
455 $ cat $TESTTMP/status-race-lock.log
478
456
479 final cleanup
457 final cleanup
480
458
481 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
459 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
482 $ cd ..
460 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now