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