##// END OF EJS Templates
rust-dirstatemap: implement part of the `setparents` logic...
Raphaël Gomès -
r50011:3df46f3a default
parent child Browse files
Show More
@@ -1,709 +1,694 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 import errno
7 import errno
8
8
9 from .i18n import _
9 from .i18n import _
10
10
11 from . import (
11 from . import (
12 error,
12 error,
13 pathutil,
13 pathutil,
14 policy,
14 policy,
15 txnutil,
15 txnutil,
16 util,
16 util,
17 )
17 )
18
18
19 from .dirstateutils import (
19 from .dirstateutils import (
20 docket as docketmod,
20 docket as docketmod,
21 v2,
21 v2,
22 )
22 )
23
23
24 parsers = policy.importmod('parsers')
24 parsers = policy.importmod('parsers')
25 rustmod = policy.importrust('dirstate')
25 rustmod = policy.importrust('dirstate')
26
26
27 propertycache = util.propertycache
27 propertycache = util.propertycache
28
28
29 if rustmod is None:
29 if rustmod is None:
30 DirstateItem = parsers.DirstateItem
30 DirstateItem = parsers.DirstateItem
31 else:
31 else:
32 DirstateItem = rustmod.DirstateItem
32 DirstateItem = rustmod.DirstateItem
33
33
34 rangemask = 0x7FFFFFFF
34 rangemask = 0x7FFFFFFF
35
35
36
36
37 class _dirstatemapcommon:
37 class _dirstatemapcommon:
38 """
38 """
39 Methods that are identical for both implementations of the dirstatemap
39 Methods that are identical for both implementations of the dirstatemap
40 class, with and without Rust extensions enabled.
40 class, with and without Rust extensions enabled.
41 """
41 """
42
42
43 # please pytype
43 # please pytype
44
44
45 _map = None
45 _map = None
46 copymap = None
46 copymap = None
47
47
48 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
48 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
49 self._use_dirstate_v2 = use_dirstate_v2
49 self._use_dirstate_v2 = use_dirstate_v2
50 self._nodeconstants = nodeconstants
50 self._nodeconstants = nodeconstants
51 self._ui = ui
51 self._ui = ui
52 self._opener = opener
52 self._opener = opener
53 self._root = root
53 self._root = root
54 self._filename = b'dirstate'
54 self._filename = b'dirstate'
55 self._nodelen = 20 # Also update Rust code when changing this!
55 self._nodelen = 20 # Also update Rust code when changing this!
56 self._parents = None
56 self._parents = None
57 self._dirtyparents = False
57 self._dirtyparents = False
58 self._docket = None
58 self._docket = None
59
59
60 # for consistent view between _pl() and _read() invocations
60 # for consistent view between _pl() and _read() invocations
61 self._pendingmode = None
61 self._pendingmode = None
62
62
63 def preload(self):
63 def preload(self):
64 """Loads the underlying data, if it's not already loaded"""
64 """Loads the underlying data, if it's not already loaded"""
65 self._map
65 self._map
66
66
67 def get(self, key, default=None):
67 def get(self, key, default=None):
68 return self._map.get(key, default)
68 return self._map.get(key, default)
69
69
70 def __len__(self):
70 def __len__(self):
71 return len(self._map)
71 return len(self._map)
72
72
73 def __iter__(self):
73 def __iter__(self):
74 return iter(self._map)
74 return iter(self._map)
75
75
76 def __contains__(self, key):
76 def __contains__(self, key):
77 return key in self._map
77 return key in self._map
78
78
79 def __getitem__(self, item):
79 def __getitem__(self, item):
80 return self._map[item]
80 return self._map[item]
81
81
82 ### disk interaction
82 ### disk interaction
83
83
84 def _opendirstatefile(self):
84 def _opendirstatefile(self):
85 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
85 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
86 if self._pendingmode is not None and self._pendingmode != mode:
86 if self._pendingmode is not None and self._pendingmode != mode:
87 fp.close()
87 fp.close()
88 raise error.Abort(
88 raise error.Abort(
89 _(b'working directory state may be changed parallelly')
89 _(b'working directory state may be changed parallelly')
90 )
90 )
91 self._pendingmode = mode
91 self._pendingmode = mode
92 return fp
92 return fp
93
93
94 def _readdirstatefile(self, size=-1):
94 def _readdirstatefile(self, size=-1):
95 try:
95 try:
96 with self._opendirstatefile() as fp:
96 with self._opendirstatefile() as fp:
97 return fp.read(size)
97 return fp.read(size)
98 except IOError as err:
98 except IOError as err:
99 if err.errno != errno.ENOENT:
99 if err.errno != errno.ENOENT:
100 raise
100 raise
101 # File doesn't exist, so the current state is empty
101 # File doesn't exist, so the current state is empty
102 return b''
102 return b''
103
103
104 @property
104 @property
105 def docket(self):
105 def docket(self):
106 if not self._docket:
106 if not self._docket:
107 if not self._use_dirstate_v2:
107 if not self._use_dirstate_v2:
108 raise error.ProgrammingError(
108 raise error.ProgrammingError(
109 b'dirstate only has a docket in v2 format'
109 b'dirstate only has a docket in v2 format'
110 )
110 )
111 self._docket = docketmod.DirstateDocket.parse(
111 self._docket = docketmod.DirstateDocket.parse(
112 self._readdirstatefile(), self._nodeconstants
112 self._readdirstatefile(), self._nodeconstants
113 )
113 )
114 return self._docket
114 return self._docket
115
115
116 def write_v2_no_append(self, tr, st, meta, packed):
116 def write_v2_no_append(self, tr, st, meta, packed):
117 old_docket = self.docket
117 old_docket = self.docket
118 new_docket = docketmod.DirstateDocket.with_new_uuid(
118 new_docket = docketmod.DirstateDocket.with_new_uuid(
119 self.parents(), len(packed), meta
119 self.parents(), len(packed), meta
120 )
120 )
121 data_filename = new_docket.data_filename()
121 data_filename = new_docket.data_filename()
122 if tr:
122 if tr:
123 tr.add(data_filename, 0)
123 tr.add(data_filename, 0)
124 self._opener.write(data_filename, packed)
124 self._opener.write(data_filename, packed)
125 # Write the new docket after the new data file has been
125 # Write the new docket after the new data file has been
126 # written. Because `st` was opened with `atomictemp=True`,
126 # written. Because `st` was opened with `atomictemp=True`,
127 # the actual `.hg/dirstate` file is only affected on close.
127 # the actual `.hg/dirstate` file is only affected on close.
128 st.write(new_docket.serialize())
128 st.write(new_docket.serialize())
129 st.close()
129 st.close()
130 # Remove the old data file after the new docket pointing to
130 # Remove the old data file after the new docket pointing to
131 # the new data file was written.
131 # the new data file was written.
132 if old_docket.uuid:
132 if old_docket.uuid:
133 data_filename = old_docket.data_filename()
133 data_filename = old_docket.data_filename()
134 unlink = lambda _tr=None: self._opener.unlink(data_filename)
134 unlink = lambda _tr=None: self._opener.unlink(data_filename)
135 if tr:
135 if tr:
136 category = b"dirstate-v2-clean-" + old_docket.uuid
136 category = b"dirstate-v2-clean-" + old_docket.uuid
137 tr.addpostclose(category, unlink)
137 tr.addpostclose(category, unlink)
138 else:
138 else:
139 unlink()
139 unlink()
140 self._docket = new_docket
140 self._docket = new_docket
141
141
142 ### reading/setting parents
142 ### reading/setting parents
143
143
144 def parents(self):
144 def parents(self):
145 if not self._parents:
145 if not self._parents:
146 if self._use_dirstate_v2:
146 if self._use_dirstate_v2:
147 self._parents = self.docket.parents
147 self._parents = self.docket.parents
148 else:
148 else:
149 read_len = self._nodelen * 2
149 read_len = self._nodelen * 2
150 st = self._readdirstatefile(read_len)
150 st = self._readdirstatefile(read_len)
151 l = len(st)
151 l = len(st)
152 if l == read_len:
152 if l == read_len:
153 self._parents = (
153 self._parents = (
154 st[: self._nodelen],
154 st[: self._nodelen],
155 st[self._nodelen : 2 * self._nodelen],
155 st[self._nodelen : 2 * self._nodelen],
156 )
156 )
157 elif l == 0:
157 elif l == 0:
158 self._parents = (
158 self._parents = (
159 self._nodeconstants.nullid,
159 self._nodeconstants.nullid,
160 self._nodeconstants.nullid,
160 self._nodeconstants.nullid,
161 )
161 )
162 else:
162 else:
163 raise error.Abort(
163 raise error.Abort(
164 _(b'working directory state appears damaged!')
164 _(b'working directory state appears damaged!')
165 )
165 )
166
166
167 return self._parents
167 return self._parents
168
168
169
169
170 class dirstatemap(_dirstatemapcommon):
170 class dirstatemap(_dirstatemapcommon):
171 """Map encapsulating the dirstate's contents.
171 """Map encapsulating the dirstate's contents.
172
172
173 The dirstate contains the following state:
173 The dirstate contains the following state:
174
174
175 - `identity` is the identity of the dirstate file, which can be used to
175 - `identity` is the identity of the dirstate file, which can be used to
176 detect when changes have occurred to the dirstate file.
176 detect when changes have occurred to the dirstate file.
177
177
178 - `parents` is a pair containing the parents of the working copy. The
178 - `parents` is a pair containing the parents of the working copy. The
179 parents are updated by calling `setparents`.
179 parents are updated by calling `setparents`.
180
180
181 - the state map maps filenames to tuples of (state, mode, size, mtime),
181 - the state map maps filenames to tuples of (state, mode, size, mtime),
182 where state is a single character representing 'normal', 'added',
182 where state is a single character representing 'normal', 'added',
183 'removed', or 'merged'. It is read by treating the dirstate as a
183 'removed', or 'merged'. It is read by treating the dirstate as a
184 dict. File state is updated by calling various methods (see each
184 dict. File state is updated by calling various methods (see each
185 documentation for details):
185 documentation for details):
186
186
187 - `reset_state`,
187 - `reset_state`,
188 - `set_tracked`
188 - `set_tracked`
189 - `set_untracked`
189 - `set_untracked`
190 - `set_clean`
190 - `set_clean`
191 - `set_possibly_dirty`
191 - `set_possibly_dirty`
192
192
193 - `copymap` maps destination filenames to their source filename.
193 - `copymap` maps destination filenames to their source filename.
194
194
195 The dirstate also provides the following views onto the state:
195 The dirstate also provides the following views onto the state:
196
196
197 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
197 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
198 form that they appear as in the dirstate.
198 form that they appear as in the dirstate.
199
199
200 - `dirfoldmap` is a dict mapping normalized directory names to the
200 - `dirfoldmap` is a dict mapping normalized directory names to the
201 denormalized form that they appear as in the dirstate.
201 denormalized form that they appear as in the dirstate.
202 """
202 """
203
203
204 ### Core data storage and access
204 ### Core data storage and access
205
205
206 @propertycache
206 @propertycache
207 def _map(self):
207 def _map(self):
208 self._map = {}
208 self._map = {}
209 self.read()
209 self.read()
210 return self._map
210 return self._map
211
211
212 @propertycache
212 @propertycache
213 def copymap(self):
213 def copymap(self):
214 self.copymap = {}
214 self.copymap = {}
215 self._map
215 self._map
216 return self.copymap
216 return self.copymap
217
217
218 def clear(self):
218 def clear(self):
219 self._map.clear()
219 self._map.clear()
220 self.copymap.clear()
220 self.copymap.clear()
221 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
221 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
222 util.clearcachedproperty(self, b"_dirs")
222 util.clearcachedproperty(self, b"_dirs")
223 util.clearcachedproperty(self, b"_alldirs")
223 util.clearcachedproperty(self, b"_alldirs")
224 util.clearcachedproperty(self, b"filefoldmap")
224 util.clearcachedproperty(self, b"filefoldmap")
225 util.clearcachedproperty(self, b"dirfoldmap")
225 util.clearcachedproperty(self, b"dirfoldmap")
226
226
227 def items(self):
227 def items(self):
228 return self._map.items()
228 return self._map.items()
229
229
230 # forward for python2,3 compat
230 # forward for python2,3 compat
231 iteritems = items
231 iteritems = items
232
232
233 def debug_iter(self, all):
233 def debug_iter(self, all):
234 """
234 """
235 Return an iterator of (filename, state, mode, size, mtime) tuples
235 Return an iterator of (filename, state, mode, size, mtime) tuples
236
236
237 `all` is unused when Rust is not enabled
237 `all` is unused when Rust is not enabled
238 """
238 """
239 for (filename, item) in self.items():
239 for (filename, item) in self.items():
240 yield (filename, item.state, item.mode, item.size, item.mtime)
240 yield (filename, item.state, item.mode, item.size, item.mtime)
241
241
242 def keys(self):
242 def keys(self):
243 return self._map.keys()
243 return self._map.keys()
244
244
245 ### reading/setting parents
245 ### reading/setting parents
246
246
247 def setparents(self, p1, p2, fold_p2=False):
247 def setparents(self, p1, p2, fold_p2=False):
248 self._parents = (p1, p2)
248 self._parents = (p1, p2)
249 self._dirtyparents = True
249 self._dirtyparents = True
250 copies = {}
250 copies = {}
251 if fold_p2:
251 if fold_p2:
252 for f, s in self._map.items():
252 for f, s in self._map.items():
253 # Discard "merged" markers when moving away from a merge state
253 # Discard "merged" markers when moving away from a merge state
254 if s.p2_info:
254 if s.p2_info:
255 source = self.copymap.pop(f, None)
255 source = self.copymap.pop(f, None)
256 if source:
256 if source:
257 copies[f] = source
257 copies[f] = source
258 s.drop_merge_data()
258 s.drop_merge_data()
259 return copies
259 return copies
260
260
261 ### disk interaction
261 ### disk interaction
262
262
263 def read(self):
263 def read(self):
264 # ignore HG_PENDING because identity is used only for writing
264 # ignore HG_PENDING because identity is used only for writing
265 self.identity = util.filestat.frompath(
265 self.identity = util.filestat.frompath(
266 self._opener.join(self._filename)
266 self._opener.join(self._filename)
267 )
267 )
268
268
269 if self._use_dirstate_v2:
269 if self._use_dirstate_v2:
270 if not self.docket.uuid:
270 if not self.docket.uuid:
271 return
271 return
272 st = self._opener.read(self.docket.data_filename())
272 st = self._opener.read(self.docket.data_filename())
273 else:
273 else:
274 st = self._readdirstatefile()
274 st = self._readdirstatefile()
275
275
276 if not st:
276 if not st:
277 return
277 return
278
278
279 # TODO: adjust this estimate for dirstate-v2
279 # TODO: adjust this estimate for dirstate-v2
280 if util.safehasattr(parsers, b'dict_new_presized'):
280 if util.safehasattr(parsers, b'dict_new_presized'):
281 # Make an estimate of the number of files in the dirstate based on
281 # Make an estimate of the number of files in the dirstate based on
282 # its size. This trades wasting some memory for avoiding costly
282 # its size. This trades wasting some memory for avoiding costly
283 # resizes. Each entry have a prefix of 17 bytes followed by one or
283 # resizes. Each entry have a prefix of 17 bytes followed by one or
284 # two path names. Studies on various large-scale real-world repositories
284 # two path names. Studies on various large-scale real-world repositories
285 # found 54 bytes a reasonable upper limit for the average path names.
285 # found 54 bytes a reasonable upper limit for the average path names.
286 # Copy entries are ignored for the sake of this estimate.
286 # Copy entries are ignored for the sake of this estimate.
287 self._map = parsers.dict_new_presized(len(st) // 71)
287 self._map = parsers.dict_new_presized(len(st) // 71)
288
288
289 # Python's garbage collector triggers a GC each time a certain number
289 # Python's garbage collector triggers a GC each time a certain number
290 # of container objects (the number being defined by
290 # of container objects (the number being defined by
291 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
291 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
292 # for each file in the dirstate. The C version then immediately marks
292 # for each file in the dirstate. The C version then immediately marks
293 # them as not to be tracked by the collector. However, this has no
293 # them as not to be tracked by the collector. However, this has no
294 # effect on when GCs are triggered, only on what objects the GC looks
294 # effect on when GCs are triggered, only on what objects the GC looks
295 # into. This means that O(number of files) GCs are unavoidable.
295 # into. This means that O(number of files) GCs are unavoidable.
296 # Depending on when in the process's lifetime the dirstate is parsed,
296 # Depending on when in the process's lifetime the dirstate is parsed,
297 # this can get very expensive. As a workaround, disable GC while
297 # this can get very expensive. As a workaround, disable GC while
298 # parsing the dirstate.
298 # parsing the dirstate.
299 #
299 #
300 # (we cannot decorate the function directly since it is in a C module)
300 # (we cannot decorate the function directly since it is in a C module)
301 if self._use_dirstate_v2:
301 if self._use_dirstate_v2:
302 p = self.docket.parents
302 p = self.docket.parents
303 meta = self.docket.tree_metadata
303 meta = self.docket.tree_metadata
304 parse_dirstate = util.nogc(v2.parse_dirstate)
304 parse_dirstate = util.nogc(v2.parse_dirstate)
305 parse_dirstate(self._map, self.copymap, st, meta)
305 parse_dirstate(self._map, self.copymap, st, meta)
306 else:
306 else:
307 parse_dirstate = util.nogc(parsers.parse_dirstate)
307 parse_dirstate = util.nogc(parsers.parse_dirstate)
308 p = parse_dirstate(self._map, self.copymap, st)
308 p = parse_dirstate(self._map, self.copymap, st)
309 if not self._dirtyparents:
309 if not self._dirtyparents:
310 self.setparents(*p)
310 self.setparents(*p)
311
311
312 # Avoid excess attribute lookups by fast pathing certain checks
312 # Avoid excess attribute lookups by fast pathing certain checks
313 self.__contains__ = self._map.__contains__
313 self.__contains__ = self._map.__contains__
314 self.__getitem__ = self._map.__getitem__
314 self.__getitem__ = self._map.__getitem__
315 self.get = self._map.get
315 self.get = self._map.get
316
316
317 def write(self, tr, st):
317 def write(self, tr, st):
318 if self._use_dirstate_v2:
318 if self._use_dirstate_v2:
319 packed, meta = v2.pack_dirstate(self._map, self.copymap)
319 packed, meta = v2.pack_dirstate(self._map, self.copymap)
320 self.write_v2_no_append(tr, st, meta, packed)
320 self.write_v2_no_append(tr, st, meta, packed)
321 else:
321 else:
322 packed = parsers.pack_dirstate(
322 packed = parsers.pack_dirstate(
323 self._map, self.copymap, self.parents()
323 self._map, self.copymap, self.parents()
324 )
324 )
325 st.write(packed)
325 st.write(packed)
326 st.close()
326 st.close()
327 self._dirtyparents = False
327 self._dirtyparents = False
328
328
329 @propertycache
329 @propertycache
330 def identity(self):
330 def identity(self):
331 self._map
331 self._map
332 return self.identity
332 return self.identity
333
333
334 ### code related to maintaining and accessing "extra" property
334 ### code related to maintaining and accessing "extra" property
335 # (e.g. "has_dir")
335 # (e.g. "has_dir")
336
336
337 def _dirs_incr(self, filename, old_entry=None):
337 def _dirs_incr(self, filename, old_entry=None):
338 """increment the dirstate counter if applicable"""
338 """increment the dirstate counter if applicable"""
339 if (
339 if (
340 old_entry is None or old_entry.removed
340 old_entry is None or old_entry.removed
341 ) and "_dirs" in self.__dict__:
341 ) and "_dirs" in self.__dict__:
342 self._dirs.addpath(filename)
342 self._dirs.addpath(filename)
343 if old_entry is None and "_alldirs" in self.__dict__:
343 if old_entry is None and "_alldirs" in self.__dict__:
344 self._alldirs.addpath(filename)
344 self._alldirs.addpath(filename)
345
345
346 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
346 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
347 """decrement the dirstate counter if applicable"""
347 """decrement the dirstate counter if applicable"""
348 if old_entry is not None:
348 if old_entry is not None:
349 if "_dirs" in self.__dict__ and not old_entry.removed:
349 if "_dirs" in self.__dict__ and not old_entry.removed:
350 self._dirs.delpath(filename)
350 self._dirs.delpath(filename)
351 if "_alldirs" in self.__dict__ and not remove_variant:
351 if "_alldirs" in self.__dict__ and not remove_variant:
352 self._alldirs.delpath(filename)
352 self._alldirs.delpath(filename)
353 elif remove_variant and "_alldirs" in self.__dict__:
353 elif remove_variant and "_alldirs" in self.__dict__:
354 self._alldirs.addpath(filename)
354 self._alldirs.addpath(filename)
355 if "filefoldmap" in self.__dict__:
355 if "filefoldmap" in self.__dict__:
356 normed = util.normcase(filename)
356 normed = util.normcase(filename)
357 self.filefoldmap.pop(normed, None)
357 self.filefoldmap.pop(normed, None)
358
358
359 @propertycache
359 @propertycache
360 def filefoldmap(self):
360 def filefoldmap(self):
361 """Returns a dictionary mapping normalized case paths to their
361 """Returns a dictionary mapping normalized case paths to their
362 non-normalized versions.
362 non-normalized versions.
363 """
363 """
364 try:
364 try:
365 makefilefoldmap = parsers.make_file_foldmap
365 makefilefoldmap = parsers.make_file_foldmap
366 except AttributeError:
366 except AttributeError:
367 pass
367 pass
368 else:
368 else:
369 return makefilefoldmap(
369 return makefilefoldmap(
370 self._map, util.normcasespec, util.normcasefallback
370 self._map, util.normcasespec, util.normcasefallback
371 )
371 )
372
372
373 f = {}
373 f = {}
374 normcase = util.normcase
374 normcase = util.normcase
375 for name, s in self._map.items():
375 for name, s in self._map.items():
376 if not s.removed:
376 if not s.removed:
377 f[normcase(name)] = name
377 f[normcase(name)] = name
378 f[b'.'] = b'.' # prevents useless util.fspath() invocation
378 f[b'.'] = b'.' # prevents useless util.fspath() invocation
379 return f
379 return f
380
380
381 @propertycache
381 @propertycache
382 def dirfoldmap(self):
382 def dirfoldmap(self):
383 f = {}
383 f = {}
384 normcase = util.normcase
384 normcase = util.normcase
385 for name in self._dirs:
385 for name in self._dirs:
386 f[normcase(name)] = name
386 f[normcase(name)] = name
387 return f
387 return f
388
388
389 def hastrackeddir(self, d):
389 def hastrackeddir(self, d):
390 """
390 """
391 Returns True if the dirstate contains a tracked (not removed) file
391 Returns True if the dirstate contains a tracked (not removed) file
392 in this directory.
392 in this directory.
393 """
393 """
394 return d in self._dirs
394 return d in self._dirs
395
395
396 def hasdir(self, d):
396 def hasdir(self, d):
397 """
397 """
398 Returns True if the dirstate contains a file (tracked or removed)
398 Returns True if the dirstate contains a file (tracked or removed)
399 in this directory.
399 in this directory.
400 """
400 """
401 return d in self._alldirs
401 return d in self._alldirs
402
402
403 @propertycache
403 @propertycache
404 def _dirs(self):
404 def _dirs(self):
405 return pathutil.dirs(self._map, only_tracked=True)
405 return pathutil.dirs(self._map, only_tracked=True)
406
406
407 @propertycache
407 @propertycache
408 def _alldirs(self):
408 def _alldirs(self):
409 return pathutil.dirs(self._map)
409 return pathutil.dirs(self._map)
410
410
411 ### code related to manipulation of entries and copy-sources
411 ### code related to manipulation of entries and copy-sources
412
412
413 def reset_state(
413 def reset_state(
414 self,
414 self,
415 filename,
415 filename,
416 wc_tracked=False,
416 wc_tracked=False,
417 p1_tracked=False,
417 p1_tracked=False,
418 p2_info=False,
418 p2_info=False,
419 has_meaningful_mtime=True,
419 has_meaningful_mtime=True,
420 parentfiledata=None,
420 parentfiledata=None,
421 ):
421 ):
422 """Set a entry to a given state, diregarding all previous state
422 """Set a entry to a given state, diregarding all previous state
423
423
424 This is to be used by the part of the dirstate API dedicated to
424 This is to be used by the part of the dirstate API dedicated to
425 adjusting the dirstate after a update/merge.
425 adjusting the dirstate after a update/merge.
426
426
427 note: calling this might result to no entry existing at all if the
427 note: calling this might result to no entry existing at all if the
428 dirstate map does not see any point at having one for this file
428 dirstate map does not see any point at having one for this file
429 anymore.
429 anymore.
430 """
430 """
431 # copy information are now outdated
431 # copy information are now outdated
432 # (maybe new information should be in directly passed to this function)
432 # (maybe new information should be in directly passed to this function)
433 self.copymap.pop(filename, None)
433 self.copymap.pop(filename, None)
434
434
435 if not (p1_tracked or p2_info or wc_tracked):
435 if not (p1_tracked or p2_info or wc_tracked):
436 old_entry = self._map.get(filename)
436 old_entry = self._map.get(filename)
437 self._drop_entry(filename)
437 self._drop_entry(filename)
438 self._dirs_decr(filename, old_entry=old_entry)
438 self._dirs_decr(filename, old_entry=old_entry)
439 return
439 return
440
440
441 old_entry = self._map.get(filename)
441 old_entry = self._map.get(filename)
442 self._dirs_incr(filename, old_entry)
442 self._dirs_incr(filename, old_entry)
443 entry = DirstateItem(
443 entry = DirstateItem(
444 wc_tracked=wc_tracked,
444 wc_tracked=wc_tracked,
445 p1_tracked=p1_tracked,
445 p1_tracked=p1_tracked,
446 p2_info=p2_info,
446 p2_info=p2_info,
447 has_meaningful_mtime=has_meaningful_mtime,
447 has_meaningful_mtime=has_meaningful_mtime,
448 parentfiledata=parentfiledata,
448 parentfiledata=parentfiledata,
449 )
449 )
450 self._map[filename] = entry
450 self._map[filename] = entry
451
451
452 def set_tracked(self, filename):
452 def set_tracked(self, filename):
453 new = False
453 new = False
454 entry = self.get(filename)
454 entry = self.get(filename)
455 if entry is None:
455 if entry is None:
456 self._dirs_incr(filename)
456 self._dirs_incr(filename)
457 entry = DirstateItem(
457 entry = DirstateItem(
458 wc_tracked=True,
458 wc_tracked=True,
459 )
459 )
460
460
461 self._map[filename] = entry
461 self._map[filename] = entry
462 new = True
462 new = True
463 elif not entry.tracked:
463 elif not entry.tracked:
464 self._dirs_incr(filename, entry)
464 self._dirs_incr(filename, entry)
465 entry.set_tracked()
465 entry.set_tracked()
466 self._refresh_entry(filename, entry)
466 self._refresh_entry(filename, entry)
467 new = True
467 new = True
468 else:
468 else:
469 # XXX This is probably overkill for more case, but we need this to
469 # XXX This is probably overkill for more case, but we need this to
470 # fully replace the `normallookup` call with `set_tracked` one.
470 # fully replace the `normallookup` call with `set_tracked` one.
471 # Consider smoothing this in the future.
471 # Consider smoothing this in the future.
472 entry.set_possibly_dirty()
472 entry.set_possibly_dirty()
473 self._refresh_entry(filename, entry)
473 self._refresh_entry(filename, entry)
474 return new
474 return new
475
475
476 def set_untracked(self, f):
476 def set_untracked(self, f):
477 """Mark a file as no longer tracked in the dirstate map"""
477 """Mark a file as no longer tracked in the dirstate map"""
478 entry = self.get(f)
478 entry = self.get(f)
479 if entry is None:
479 if entry is None:
480 return False
480 return False
481 else:
481 else:
482 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
482 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
483 if not entry.p2_info:
483 if not entry.p2_info:
484 self.copymap.pop(f, None)
484 self.copymap.pop(f, None)
485 entry.set_untracked()
485 entry.set_untracked()
486 self._refresh_entry(f, entry)
486 self._refresh_entry(f, entry)
487 return True
487 return True
488
488
489 def set_clean(self, filename, mode, size, mtime):
489 def set_clean(self, filename, mode, size, mtime):
490 """mark a file as back to a clean state"""
490 """mark a file as back to a clean state"""
491 entry = self[filename]
491 entry = self[filename]
492 size = size & rangemask
492 size = size & rangemask
493 entry.set_clean(mode, size, mtime)
493 entry.set_clean(mode, size, mtime)
494 self._refresh_entry(filename, entry)
494 self._refresh_entry(filename, entry)
495 self.copymap.pop(filename, None)
495 self.copymap.pop(filename, None)
496
496
497 def set_possibly_dirty(self, filename):
497 def set_possibly_dirty(self, filename):
498 """record that the current state of the file on disk is unknown"""
498 """record that the current state of the file on disk is unknown"""
499 entry = self[filename]
499 entry = self[filename]
500 entry.set_possibly_dirty()
500 entry.set_possibly_dirty()
501 self._refresh_entry(filename, entry)
501 self._refresh_entry(filename, entry)
502
502
503 def _refresh_entry(self, f, entry):
503 def _refresh_entry(self, f, entry):
504 """record updated state of an entry"""
504 """record updated state of an entry"""
505 if not entry.any_tracked:
505 if not entry.any_tracked:
506 self._map.pop(f, None)
506 self._map.pop(f, None)
507
507
508 def _drop_entry(self, f):
508 def _drop_entry(self, f):
509 """remove any entry for file f
509 """remove any entry for file f
510
510
511 This should also drop associated copy information
511 This should also drop associated copy information
512
512
513 The fact we actually need to drop it is the responsability of the caller"""
513 The fact we actually need to drop it is the responsability of the caller"""
514 self._map.pop(f, None)
514 self._map.pop(f, None)
515 self.copymap.pop(f, None)
515 self.copymap.pop(f, None)
516
516
517
517
518 if rustmod is not None:
518 if rustmod is not None:
519
519
520 class dirstatemap(_dirstatemapcommon):
520 class dirstatemap(_dirstatemapcommon):
521
521
522 ### Core data storage and access
522 ### Core data storage and access
523
523
524 @propertycache
524 @propertycache
525 def _map(self):
525 def _map(self):
526 """
526 """
527 Fills the Dirstatemap when called.
527 Fills the Dirstatemap when called.
528 """
528 """
529 # ignore HG_PENDING because identity is used only for writing
529 # ignore HG_PENDING because identity is used only for writing
530 self.identity = util.filestat.frompath(
530 self.identity = util.filestat.frompath(
531 self._opener.join(self._filename)
531 self._opener.join(self._filename)
532 )
532 )
533
533
534 if self._use_dirstate_v2:
534 if self._use_dirstate_v2:
535 if self.docket.uuid:
535 if self.docket.uuid:
536 # TODO: use mmap when possible
536 # TODO: use mmap when possible
537 data = self._opener.read(self.docket.data_filename())
537 data = self._opener.read(self.docket.data_filename())
538 else:
538 else:
539 data = b''
539 data = b''
540 self._map = rustmod.DirstateMap.new_v2(
540 self._map = rustmod.DirstateMap.new_v2(
541 data, self.docket.data_size, self.docket.tree_metadata
541 data, self.docket.data_size, self.docket.tree_metadata
542 )
542 )
543 parents = self.docket.parents
543 parents = self.docket.parents
544 else:
544 else:
545 self._map, parents = rustmod.DirstateMap.new_v1(
545 self._map, parents = rustmod.DirstateMap.new_v1(
546 self._readdirstatefile()
546 self._readdirstatefile()
547 )
547 )
548
548
549 if parents and not self._dirtyparents:
549 if parents and not self._dirtyparents:
550 self.setparents(*parents)
550 self.setparents(*parents)
551
551
552 self.__contains__ = self._map.__contains__
552 self.__contains__ = self._map.__contains__
553 self.__getitem__ = self._map.__getitem__
553 self.__getitem__ = self._map.__getitem__
554 self.get = self._map.get
554 self.get = self._map.get
555 return self._map
555 return self._map
556
556
557 @property
557 @property
558 def copymap(self):
558 def copymap(self):
559 return self._map.copymap()
559 return self._map.copymap()
560
560
561 def debug_iter(self, all):
561 def debug_iter(self, all):
562 """
562 """
563 Return an iterator of (filename, state, mode, size, mtime) tuples
563 Return an iterator of (filename, state, mode, size, mtime) tuples
564
564
565 `all`: also include with `state == b' '` dirstate tree nodes that
565 `all`: also include with `state == b' '` dirstate tree nodes that
566 don't have an associated `DirstateItem`.
566 don't have an associated `DirstateItem`.
567
567
568 """
568 """
569 return self._map.debug_iter(all)
569 return self._map.debug_iter(all)
570
570
571 def clear(self):
571 def clear(self):
572 self._map.clear()
572 self._map.clear()
573 self.setparents(
573 self.setparents(
574 self._nodeconstants.nullid, self._nodeconstants.nullid
574 self._nodeconstants.nullid, self._nodeconstants.nullid
575 )
575 )
576 util.clearcachedproperty(self, b"_dirs")
576 util.clearcachedproperty(self, b"_dirs")
577 util.clearcachedproperty(self, b"_alldirs")
577 util.clearcachedproperty(self, b"_alldirs")
578 util.clearcachedproperty(self, b"dirfoldmap")
578 util.clearcachedproperty(self, b"dirfoldmap")
579
579
580 def items(self):
580 def items(self):
581 return self._map.items()
581 return self._map.items()
582
582
583 # forward for python2,3 compat
583 # forward for python2,3 compat
584 iteritems = items
584 iteritems = items
585
585
586 def keys(self):
586 def keys(self):
587 return iter(self._map)
587 return iter(self._map)
588
588
589 ### reading/setting parents
589 ### reading/setting parents
590
590
591 def setparents(self, p1, p2, fold_p2=False):
591 def setparents(self, p1, p2, fold_p2=False):
592 self._parents = (p1, p2)
592 self._parents = (p1, p2)
593 self._dirtyparents = True
593 self._dirtyparents = True
594 copies = {}
594 copies = {}
595 if fold_p2:
595 if fold_p2:
596 # Collect into an intermediate list to avoid a `RuntimeError`
596 copies = self._map.setparents_fixup()
597 # exception due to mutation during iteration.
598 # TODO: move this the whole loop to Rust where `iter_mut`
599 # enables in-place mutation of elements of a collection while
600 # iterating it, without mutating the collection itself.
601 files_with_p2_info = [
602 f for f, s in self._map.items() if s.p2_info
603 ]
604 rust_map = self._map
605 for f in files_with_p2_info:
606 e = rust_map.get(f)
607 source = self.copymap.pop(f, None)
608 if source:
609 copies[f] = source
610 e.drop_merge_data()
611 rust_map.set_dirstate_item(f, e)
612 return copies
597 return copies
613
598
614 ### disk interaction
599 ### disk interaction
615
600
616 @propertycache
601 @propertycache
617 def identity(self):
602 def identity(self):
618 self._map
603 self._map
619 return self.identity
604 return self.identity
620
605
621 def write(self, tr, st):
606 def write(self, tr, st):
622 if not self._use_dirstate_v2:
607 if not self._use_dirstate_v2:
623 p1, p2 = self.parents()
608 p1, p2 = self.parents()
624 packed = self._map.write_v1(p1, p2)
609 packed = self._map.write_v1(p1, p2)
625 st.write(packed)
610 st.write(packed)
626 st.close()
611 st.close()
627 self._dirtyparents = False
612 self._dirtyparents = False
628 return
613 return
629
614
630 # We can only append to an existing data file if there is one
615 # We can only append to an existing data file if there is one
631 can_append = self.docket.uuid is not None
616 can_append = self.docket.uuid is not None
632 packed, meta, append = self._map.write_v2(can_append)
617 packed, meta, append = self._map.write_v2(can_append)
633 if append:
618 if append:
634 docket = self.docket
619 docket = self.docket
635 data_filename = docket.data_filename()
620 data_filename = docket.data_filename()
636 if tr:
621 if tr:
637 tr.add(data_filename, docket.data_size)
622 tr.add(data_filename, docket.data_size)
638 with self._opener(data_filename, b'r+b') as fp:
623 with self._opener(data_filename, b'r+b') as fp:
639 fp.seek(docket.data_size)
624 fp.seek(docket.data_size)
640 assert fp.tell() == docket.data_size
625 assert fp.tell() == docket.data_size
641 written = fp.write(packed)
626 written = fp.write(packed)
642 if written is not None: # py2 may return None
627 if written is not None: # py2 may return None
643 assert written == len(packed), (written, len(packed))
628 assert written == len(packed), (written, len(packed))
644 docket.data_size += len(packed)
629 docket.data_size += len(packed)
645 docket.parents = self.parents()
630 docket.parents = self.parents()
646 docket.tree_metadata = meta
631 docket.tree_metadata = meta
647 st.write(docket.serialize())
632 st.write(docket.serialize())
648 st.close()
633 st.close()
649 else:
634 else:
650 self.write_v2_no_append(tr, st, meta, packed)
635 self.write_v2_no_append(tr, st, meta, packed)
651 # Reload from the newly-written file
636 # Reload from the newly-written file
652 util.clearcachedproperty(self, b"_map")
637 util.clearcachedproperty(self, b"_map")
653 self._dirtyparents = False
638 self._dirtyparents = False
654
639
655 ### code related to maintaining and accessing "extra" property
640 ### code related to maintaining and accessing "extra" property
656 # (e.g. "has_dir")
641 # (e.g. "has_dir")
657
642
658 @propertycache
643 @propertycache
659 def filefoldmap(self):
644 def filefoldmap(self):
660 """Returns a dictionary mapping normalized case paths to their
645 """Returns a dictionary mapping normalized case paths to their
661 non-normalized versions.
646 non-normalized versions.
662 """
647 """
663 return self._map.filefoldmapasdict()
648 return self._map.filefoldmapasdict()
664
649
665 def hastrackeddir(self, d):
650 def hastrackeddir(self, d):
666 return self._map.hastrackeddir(d)
651 return self._map.hastrackeddir(d)
667
652
668 def hasdir(self, d):
653 def hasdir(self, d):
669 return self._map.hasdir(d)
654 return self._map.hasdir(d)
670
655
671 @propertycache
656 @propertycache
672 def dirfoldmap(self):
657 def dirfoldmap(self):
673 f = {}
658 f = {}
674 normcase = util.normcase
659 normcase = util.normcase
675 for name in self._map.tracked_dirs():
660 for name in self._map.tracked_dirs():
676 f[normcase(name)] = name
661 f[normcase(name)] = name
677 return f
662 return f
678
663
679 ### code related to manipulation of entries and copy-sources
664 ### code related to manipulation of entries and copy-sources
680
665
681 def set_tracked(self, f):
666 def set_tracked(self, f):
682 return self._map.set_tracked(f)
667 return self._map.set_tracked(f)
683
668
684 def set_untracked(self, f):
669 def set_untracked(self, f):
685 return self._map.set_untracked(f)
670 return self._map.set_untracked(f)
686
671
687 def set_clean(self, filename, mode, size, mtime):
672 def set_clean(self, filename, mode, size, mtime):
688 self._map.set_clean(filename, mode, size, mtime)
673 self._map.set_clean(filename, mode, size, mtime)
689
674
690 def set_possibly_dirty(self, f):
675 def set_possibly_dirty(self, f):
691 self._map.set_possibly_dirty(f)
676 self._map.set_possibly_dirty(f)
692
677
693 def reset_state(
678 def reset_state(
694 self,
679 self,
695 filename,
680 filename,
696 wc_tracked=False,
681 wc_tracked=False,
697 p1_tracked=False,
682 p1_tracked=False,
698 p2_info=False,
683 p2_info=False,
699 has_meaningful_mtime=True,
684 has_meaningful_mtime=True,
700 parentfiledata=None,
685 parentfiledata=None,
701 ):
686 ):
702 return self._map.reset_state(
687 return self._map.reset_state(
703 filename,
688 filename,
704 wc_tracked,
689 wc_tracked,
705 p1_tracked,
690 p1_tracked,
706 p2_info,
691 p2_info,
707 has_meaningful_mtime,
692 has_meaningful_mtime,
708 parentfiledata,
693 parentfiledata,
709 )
694 )
@@ -1,1404 +1,1439 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::EntryState;
25 use crate::EntryState;
26 use crate::FastHashbrownMap as FastHashMap;
26 use crate::FastHashbrownMap as FastHashMap;
27 use crate::PatternFileWarning;
27 use crate::PatternFileWarning;
28 use crate::StatusError;
28 use crate::StatusError;
29 use crate::StatusOptions;
29 use crate::StatusOptions;
30
30
31 /// Append to an existing data file if the amount of unreachable data (not used
31 /// Append to an existing data file if the amount of unreachable data (not used
32 /// anymore) is less than this fraction of the total amount of existing data.
32 /// anymore) is less than this fraction of the total amount of existing data.
33 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
33 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
34
34
35 pub struct DirstateMap<'on_disk> {
35 pub struct DirstateMap<'on_disk> {
36 /// Contents of the `.hg/dirstate` file
36 /// Contents of the `.hg/dirstate` file
37 pub(super) on_disk: &'on_disk [u8],
37 pub(super) on_disk: &'on_disk [u8],
38
38
39 pub(super) root: ChildNodes<'on_disk>,
39 pub(super) root: ChildNodes<'on_disk>,
40
40
41 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
41 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
42 pub(super) nodes_with_entry_count: u32,
42 pub(super) nodes_with_entry_count: u32,
43
43
44 /// Number of nodes anywhere in the tree that have
44 /// Number of nodes anywhere in the tree that have
45 /// `.copy_source.is_some()`.
45 /// `.copy_source.is_some()`.
46 pub(super) nodes_with_copy_source_count: u32,
46 pub(super) nodes_with_copy_source_count: u32,
47
47
48 /// See on_disk::Header
48 /// See on_disk::Header
49 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
49 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
50
50
51 /// How many bytes of `on_disk` are not used anymore
51 /// How many bytes of `on_disk` are not used anymore
52 pub(super) unreachable_bytes: u32,
52 pub(super) unreachable_bytes: u32,
53 }
53 }
54
54
55 /// Using a plain `HgPathBuf` of the full path from the repository root as a
55 /// Using a plain `HgPathBuf` of the full path from the repository root as a
56 /// map key would also work: all paths in a given map have the same parent
56 /// map key would also work: all paths in a given map have the same parent
57 /// path, so comparing full paths gives the same result as comparing base
57 /// path, so comparing full paths gives the same result as comparing base
58 /// names. However `HashMap` would waste time always re-hashing the same
58 /// names. However `HashMap` would waste time always re-hashing the same
59 /// string prefix.
59 /// string prefix.
60 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
60 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
61
61
62 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
62 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
63 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
63 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
64 pub(super) enum BorrowedPath<'tree, 'on_disk> {
64 pub(super) enum BorrowedPath<'tree, 'on_disk> {
65 InMemory(&'tree HgPathBuf),
65 InMemory(&'tree HgPathBuf),
66 OnDisk(&'on_disk HgPath),
66 OnDisk(&'on_disk HgPath),
67 }
67 }
68
68
69 pub(super) enum ChildNodes<'on_disk> {
69 pub(super) enum ChildNodes<'on_disk> {
70 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
70 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
71 OnDisk(&'on_disk [on_disk::Node]),
71 OnDisk(&'on_disk [on_disk::Node]),
72 }
72 }
73
73
74 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
74 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
75 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
75 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
76 OnDisk(&'on_disk [on_disk::Node]),
76 OnDisk(&'on_disk [on_disk::Node]),
77 }
77 }
78
78
79 pub(super) enum NodeRef<'tree, 'on_disk> {
79 pub(super) enum NodeRef<'tree, 'on_disk> {
80 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
80 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
81 OnDisk(&'on_disk on_disk::Node),
81 OnDisk(&'on_disk on_disk::Node),
82 }
82 }
83
83
84 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
84 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
85 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
85 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
86 match *self {
86 match *self {
87 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
87 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
88 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
88 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
89 }
89 }
90 }
90 }
91 }
91 }
92
92
93 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
93 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
94 type Target = HgPath;
94 type Target = HgPath;
95
95
96 fn deref(&self) -> &HgPath {
96 fn deref(&self) -> &HgPath {
97 match *self {
97 match *self {
98 BorrowedPath::InMemory(in_memory) => in_memory,
98 BorrowedPath::InMemory(in_memory) => in_memory,
99 BorrowedPath::OnDisk(on_disk) => on_disk,
99 BorrowedPath::OnDisk(on_disk) => on_disk,
100 }
100 }
101 }
101 }
102 }
102 }
103
103
104 impl Default for ChildNodes<'_> {
104 impl Default for ChildNodes<'_> {
105 fn default() -> Self {
105 fn default() -> Self {
106 ChildNodes::InMemory(Default::default())
106 ChildNodes::InMemory(Default::default())
107 }
107 }
108 }
108 }
109
109
110 impl<'on_disk> ChildNodes<'on_disk> {
110 impl<'on_disk> ChildNodes<'on_disk> {
111 pub(super) fn as_ref<'tree>(
111 pub(super) fn as_ref<'tree>(
112 &'tree self,
112 &'tree self,
113 ) -> ChildNodesRef<'tree, 'on_disk> {
113 ) -> ChildNodesRef<'tree, 'on_disk> {
114 match self {
114 match self {
115 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
115 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
116 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
116 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
117 }
117 }
118 }
118 }
119
119
120 pub(super) fn is_empty(&self) -> bool {
120 pub(super) fn is_empty(&self) -> bool {
121 match self {
121 match self {
122 ChildNodes::InMemory(nodes) => nodes.is_empty(),
122 ChildNodes::InMemory(nodes) => nodes.is_empty(),
123 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
123 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
124 }
124 }
125 }
125 }
126
126
127 fn make_mut(
127 fn make_mut(
128 &mut self,
128 &mut self,
129 on_disk: &'on_disk [u8],
129 on_disk: &'on_disk [u8],
130 unreachable_bytes: &mut u32,
130 unreachable_bytes: &mut u32,
131 ) -> Result<
131 ) -> Result<
132 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
132 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
133 DirstateV2ParseError,
133 DirstateV2ParseError,
134 > {
134 > {
135 match self {
135 match self {
136 ChildNodes::InMemory(nodes) => Ok(nodes),
136 ChildNodes::InMemory(nodes) => Ok(nodes),
137 ChildNodes::OnDisk(nodes) => {
137 ChildNodes::OnDisk(nodes) => {
138 *unreachable_bytes +=
138 *unreachable_bytes +=
139 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
139 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
140 let nodes = nodes
140 let nodes = nodes
141 .iter()
141 .iter()
142 .map(|node| {
142 .map(|node| {
143 Ok((
143 Ok((
144 node.path(on_disk)?,
144 node.path(on_disk)?,
145 node.to_in_memory_node(on_disk)?,
145 node.to_in_memory_node(on_disk)?,
146 ))
146 ))
147 })
147 })
148 .collect::<Result<_, _>>()?;
148 .collect::<Result<_, _>>()?;
149 *self = ChildNodes::InMemory(nodes);
149 *self = ChildNodes::InMemory(nodes);
150 match self {
150 match self {
151 ChildNodes::InMemory(nodes) => Ok(nodes),
151 ChildNodes::InMemory(nodes) => Ok(nodes),
152 ChildNodes::OnDisk(_) => unreachable!(),
152 ChildNodes::OnDisk(_) => unreachable!(),
153 }
153 }
154 }
154 }
155 }
155 }
156 }
156 }
157 }
157 }
158
158
159 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
159 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
160 pub(super) fn get(
160 pub(super) fn get(
161 &self,
161 &self,
162 base_name: &HgPath,
162 base_name: &HgPath,
163 on_disk: &'on_disk [u8],
163 on_disk: &'on_disk [u8],
164 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
164 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
165 match self {
165 match self {
166 ChildNodesRef::InMemory(nodes) => Ok(nodes
166 ChildNodesRef::InMemory(nodes) => Ok(nodes
167 .get_key_value(base_name)
167 .get_key_value(base_name)
168 .map(|(k, v)| NodeRef::InMemory(k, v))),
168 .map(|(k, v)| NodeRef::InMemory(k, v))),
169 ChildNodesRef::OnDisk(nodes) => {
169 ChildNodesRef::OnDisk(nodes) => {
170 let mut parse_result = Ok(());
170 let mut parse_result = Ok(());
171 let search_result = nodes.binary_search_by(|node| {
171 let search_result = nodes.binary_search_by(|node| {
172 match node.base_name(on_disk) {
172 match node.base_name(on_disk) {
173 Ok(node_base_name) => node_base_name.cmp(base_name),
173 Ok(node_base_name) => node_base_name.cmp(base_name),
174 Err(e) => {
174 Err(e) => {
175 parse_result = Err(e);
175 parse_result = Err(e);
176 // Dummy comparison result, `search_result` won’t
176 // Dummy comparison result, `search_result` won’t
177 // be used since `parse_result` is an error
177 // be used since `parse_result` is an error
178 std::cmp::Ordering::Equal
178 std::cmp::Ordering::Equal
179 }
179 }
180 }
180 }
181 });
181 });
182 parse_result.map(|()| {
182 parse_result.map(|()| {
183 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
183 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
184 })
184 })
185 }
185 }
186 }
186 }
187 }
187 }
188
188
189 /// Iterate in undefined order
189 /// Iterate in undefined order
190 pub(super) fn iter(
190 pub(super) fn iter(
191 &self,
191 &self,
192 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
192 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
193 match self {
193 match self {
194 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
194 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
195 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
195 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
196 ),
196 ),
197 ChildNodesRef::OnDisk(nodes) => {
197 ChildNodesRef::OnDisk(nodes) => {
198 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
198 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
199 }
199 }
200 }
200 }
201 }
201 }
202
202
203 /// Iterate in parallel in undefined order
203 /// Iterate in parallel in undefined order
204 pub(super) fn par_iter(
204 pub(super) fn par_iter(
205 &self,
205 &self,
206 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
206 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
207 {
207 {
208 use rayon::prelude::*;
208 use rayon::prelude::*;
209 match self {
209 match self {
210 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
210 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
211 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
211 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
212 ),
212 ),
213 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
213 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
214 nodes.par_iter().map(NodeRef::OnDisk),
214 nodes.par_iter().map(NodeRef::OnDisk),
215 ),
215 ),
216 }
216 }
217 }
217 }
218
218
219 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
219 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
220 match self {
220 match self {
221 ChildNodesRef::InMemory(nodes) => {
221 ChildNodesRef::InMemory(nodes) => {
222 let mut vec: Vec<_> = nodes
222 let mut vec: Vec<_> = nodes
223 .iter()
223 .iter()
224 .map(|(k, v)| NodeRef::InMemory(k, v))
224 .map(|(k, v)| NodeRef::InMemory(k, v))
225 .collect();
225 .collect();
226 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
226 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
227 match node {
227 match node {
228 NodeRef::InMemory(path, _node) => path.base_name(),
228 NodeRef::InMemory(path, _node) => path.base_name(),
229 NodeRef::OnDisk(_) => unreachable!(),
229 NodeRef::OnDisk(_) => unreachable!(),
230 }
230 }
231 }
231 }
232 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
232 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
233 // value: https://github.com/rust-lang/rust/issues/34162
233 // value: https://github.com/rust-lang/rust/issues/34162
234 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
234 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
235 vec
235 vec
236 }
236 }
237 ChildNodesRef::OnDisk(nodes) => {
237 ChildNodesRef::OnDisk(nodes) => {
238 // Nodes on disk are already sorted
238 // Nodes on disk are already sorted
239 nodes.iter().map(NodeRef::OnDisk).collect()
239 nodes.iter().map(NodeRef::OnDisk).collect()
240 }
240 }
241 }
241 }
242 }
242 }
243 }
243 }
244
244
245 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
245 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
246 pub(super) fn full_path(
246 pub(super) fn full_path(
247 &self,
247 &self,
248 on_disk: &'on_disk [u8],
248 on_disk: &'on_disk [u8],
249 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
249 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
250 match self {
250 match self {
251 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
251 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
252 NodeRef::OnDisk(node) => node.full_path(on_disk),
252 NodeRef::OnDisk(node) => node.full_path(on_disk),
253 }
253 }
254 }
254 }
255
255
256 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
256 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
257 /// HgPath>` detached from `'tree`
257 /// HgPath>` detached from `'tree`
258 pub(super) fn full_path_borrowed(
258 pub(super) fn full_path_borrowed(
259 &self,
259 &self,
260 on_disk: &'on_disk [u8],
260 on_disk: &'on_disk [u8],
261 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
261 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
262 match self {
262 match self {
263 NodeRef::InMemory(path, _node) => match path.full_path() {
263 NodeRef::InMemory(path, _node) => match path.full_path() {
264 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
264 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
265 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
265 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
266 },
266 },
267 NodeRef::OnDisk(node) => {
267 NodeRef::OnDisk(node) => {
268 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
268 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
269 }
269 }
270 }
270 }
271 }
271 }
272
272
273 pub(super) fn base_name(
273 pub(super) fn base_name(
274 &self,
274 &self,
275 on_disk: &'on_disk [u8],
275 on_disk: &'on_disk [u8],
276 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
276 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
277 match self {
277 match self {
278 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
278 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
279 NodeRef::OnDisk(node) => node.base_name(on_disk),
279 NodeRef::OnDisk(node) => node.base_name(on_disk),
280 }
280 }
281 }
281 }
282
282
283 pub(super) fn children(
283 pub(super) fn children(
284 &self,
284 &self,
285 on_disk: &'on_disk [u8],
285 on_disk: &'on_disk [u8],
286 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
286 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
287 match self {
287 match self {
288 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
288 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
289 NodeRef::OnDisk(node) => {
289 NodeRef::OnDisk(node) => {
290 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
290 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
291 }
291 }
292 }
292 }
293 }
293 }
294
294
295 pub(super) fn has_copy_source(&self) -> bool {
295 pub(super) fn has_copy_source(&self) -> bool {
296 match self {
296 match self {
297 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
297 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
298 NodeRef::OnDisk(node) => node.has_copy_source(),
298 NodeRef::OnDisk(node) => node.has_copy_source(),
299 }
299 }
300 }
300 }
301
301
302 pub(super) fn copy_source(
302 pub(super) fn copy_source(
303 &self,
303 &self,
304 on_disk: &'on_disk [u8],
304 on_disk: &'on_disk [u8],
305 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
305 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
306 match self {
306 match self {
307 NodeRef::InMemory(_path, node) => {
307 NodeRef::InMemory(_path, node) => {
308 Ok(node.copy_source.as_ref().map(|s| &**s))
308 Ok(node.copy_source.as_ref().map(|s| &**s))
309 }
309 }
310 NodeRef::OnDisk(node) => node.copy_source(on_disk),
310 NodeRef::OnDisk(node) => node.copy_source(on_disk),
311 }
311 }
312 }
312 }
313 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
313 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
314 /// HgPath>` detached from `'tree`
314 /// HgPath>` detached from `'tree`
315 pub(super) fn copy_source_borrowed(
315 pub(super) fn copy_source_borrowed(
316 &self,
316 &self,
317 on_disk: &'on_disk [u8],
317 on_disk: &'on_disk [u8],
318 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
318 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
319 {
319 {
320 Ok(match self {
320 Ok(match self {
321 NodeRef::InMemory(_path, node) => {
321 NodeRef::InMemory(_path, node) => {
322 node.copy_source.as_ref().map(|source| match source {
322 node.copy_source.as_ref().map(|source| match source {
323 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
323 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
324 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
324 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
325 })
325 })
326 }
326 }
327 NodeRef::OnDisk(node) => node
327 NodeRef::OnDisk(node) => node
328 .copy_source(on_disk)?
328 .copy_source(on_disk)?
329 .map(|source| BorrowedPath::OnDisk(source)),
329 .map(|source| BorrowedPath::OnDisk(source)),
330 })
330 })
331 }
331 }
332
332
333 pub(super) fn entry(
333 pub(super) fn entry(
334 &self,
334 &self,
335 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
335 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
336 match self {
336 match self {
337 NodeRef::InMemory(_path, node) => {
337 NodeRef::InMemory(_path, node) => {
338 Ok(node.data.as_entry().copied())
338 Ok(node.data.as_entry().copied())
339 }
339 }
340 NodeRef::OnDisk(node) => node.entry(),
340 NodeRef::OnDisk(node) => node.entry(),
341 }
341 }
342 }
342 }
343
343
344 pub(super) fn state(
344 pub(super) fn state(
345 &self,
345 &self,
346 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
346 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
347 Ok(self.entry()?.and_then(|e| {
347 Ok(self.entry()?.and_then(|e| {
348 if e.any_tracked() {
348 if e.any_tracked() {
349 Some(e.state())
349 Some(e.state())
350 } else {
350 } else {
351 None
351 None
352 }
352 }
353 }))
353 }))
354 }
354 }
355
355
356 pub(super) fn cached_directory_mtime(
356 pub(super) fn cached_directory_mtime(
357 &self,
357 &self,
358 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
358 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
359 match self {
359 match self {
360 NodeRef::InMemory(_path, node) => Ok(match node.data {
360 NodeRef::InMemory(_path, node) => Ok(match node.data {
361 NodeData::CachedDirectory { mtime } => Some(mtime),
361 NodeData::CachedDirectory { mtime } => Some(mtime),
362 _ => None,
362 _ => None,
363 }),
363 }),
364 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
364 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
365 }
365 }
366 }
366 }
367
367
368 pub(super) fn descendants_with_entry_count(&self) -> u32 {
368 pub(super) fn descendants_with_entry_count(&self) -> u32 {
369 match self {
369 match self {
370 NodeRef::InMemory(_path, node) => {
370 NodeRef::InMemory(_path, node) => {
371 node.descendants_with_entry_count
371 node.descendants_with_entry_count
372 }
372 }
373 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
373 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
374 }
374 }
375 }
375 }
376
376
377 pub(super) fn tracked_descendants_count(&self) -> u32 {
377 pub(super) fn tracked_descendants_count(&self) -> u32 {
378 match self {
378 match self {
379 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
379 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
380 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
380 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
381 }
381 }
382 }
382 }
383 }
383 }
384
384
385 /// Represents a file or a directory
385 /// Represents a file or a directory
386 #[derive(Default)]
386 #[derive(Default)]
387 pub(super) struct Node<'on_disk> {
387 pub(super) struct Node<'on_disk> {
388 pub(super) data: NodeData,
388 pub(super) data: NodeData,
389
389
390 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
390 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
391
391
392 pub(super) children: ChildNodes<'on_disk>,
392 pub(super) children: ChildNodes<'on_disk>,
393
393
394 /// How many (non-inclusive) descendants of this node have an entry.
394 /// How many (non-inclusive) descendants of this node have an entry.
395 pub(super) descendants_with_entry_count: u32,
395 pub(super) descendants_with_entry_count: u32,
396
396
397 /// How many (non-inclusive) descendants of this node have an entry whose
397 /// How many (non-inclusive) descendants of this node have an entry whose
398 /// state is "tracked".
398 /// state is "tracked".
399 pub(super) tracked_descendants_count: u32,
399 pub(super) tracked_descendants_count: u32,
400 }
400 }
401
401
402 pub(super) enum NodeData {
402 pub(super) enum NodeData {
403 Entry(DirstateEntry),
403 Entry(DirstateEntry),
404 CachedDirectory { mtime: TruncatedTimestamp },
404 CachedDirectory { mtime: TruncatedTimestamp },
405 None,
405 None,
406 }
406 }
407
407
408 impl Default for NodeData {
408 impl Default for NodeData {
409 fn default() -> Self {
409 fn default() -> Self {
410 NodeData::None
410 NodeData::None
411 }
411 }
412 }
412 }
413
413
414 impl NodeData {
414 impl NodeData {
415 fn has_entry(&self) -> bool {
415 fn has_entry(&self) -> bool {
416 match self {
416 match self {
417 NodeData::Entry(_) => true,
417 NodeData::Entry(_) => true,
418 _ => false,
418 _ => false,
419 }
419 }
420 }
420 }
421
421
422 fn as_entry(&self) -> Option<&DirstateEntry> {
422 fn as_entry(&self) -> Option<&DirstateEntry> {
423 match self {
423 match self {
424 NodeData::Entry(entry) => Some(entry),
424 NodeData::Entry(entry) => Some(entry),
425 _ => None,
425 _ => None,
426 }
426 }
427 }
427 }
428
428
429 fn as_entry_mut(&mut self) -> Option<&mut DirstateEntry> {
429 fn as_entry_mut(&mut self) -> Option<&mut DirstateEntry> {
430 match self {
430 match self {
431 NodeData::Entry(entry) => Some(entry),
431 NodeData::Entry(entry) => Some(entry),
432 _ => None,
432 _ => None,
433 }
433 }
434 }
434 }
435 }
435 }
436
436
437 impl<'on_disk> DirstateMap<'on_disk> {
437 impl<'on_disk> DirstateMap<'on_disk> {
438 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
438 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
439 Self {
439 Self {
440 on_disk,
440 on_disk,
441 root: ChildNodes::default(),
441 root: ChildNodes::default(),
442 nodes_with_entry_count: 0,
442 nodes_with_entry_count: 0,
443 nodes_with_copy_source_count: 0,
443 nodes_with_copy_source_count: 0,
444 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
444 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
445 unreachable_bytes: 0,
445 unreachable_bytes: 0,
446 }
446 }
447 }
447 }
448
448
449 #[timed]
449 #[timed]
450 pub fn new_v2(
450 pub fn new_v2(
451 on_disk: &'on_disk [u8],
451 on_disk: &'on_disk [u8],
452 data_size: usize,
452 data_size: usize,
453 metadata: &[u8],
453 metadata: &[u8],
454 ) -> Result<Self, DirstateError> {
454 ) -> Result<Self, DirstateError> {
455 if let Some(data) = on_disk.get(..data_size) {
455 if let Some(data) = on_disk.get(..data_size) {
456 Ok(on_disk::read(data, metadata)?)
456 Ok(on_disk::read(data, metadata)?)
457 } else {
457 } else {
458 Err(DirstateV2ParseError.into())
458 Err(DirstateV2ParseError.into())
459 }
459 }
460 }
460 }
461
461
462 #[timed]
462 #[timed]
463 pub fn new_v1(
463 pub fn new_v1(
464 on_disk: &'on_disk [u8],
464 on_disk: &'on_disk [u8],
465 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
465 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
466 let mut map = Self::empty(on_disk);
466 let mut map = Self::empty(on_disk);
467 if map.on_disk.is_empty() {
467 if map.on_disk.is_empty() {
468 return Ok((map, None));
468 return Ok((map, None));
469 }
469 }
470
470
471 let parents = parse_dirstate_entries(
471 let parents = parse_dirstate_entries(
472 map.on_disk,
472 map.on_disk,
473 |path, entry, copy_source| {
473 |path, entry, copy_source| {
474 let tracked = entry.state().is_tracked();
474 let tracked = entry.state().is_tracked();
475 let node = Self::get_or_insert_node(
475 let node = Self::get_or_insert_node(
476 map.on_disk,
476 map.on_disk,
477 &mut map.unreachable_bytes,
477 &mut map.unreachable_bytes,
478 &mut map.root,
478 &mut map.root,
479 path,
479 path,
480 WithBasename::to_cow_borrowed,
480 WithBasename::to_cow_borrowed,
481 |ancestor| {
481 |ancestor| {
482 if tracked {
482 if tracked {
483 ancestor.tracked_descendants_count += 1
483 ancestor.tracked_descendants_count += 1
484 }
484 }
485 ancestor.descendants_with_entry_count += 1
485 ancestor.descendants_with_entry_count += 1
486 },
486 },
487 )?;
487 )?;
488 assert!(
488 assert!(
489 !node.data.has_entry(),
489 !node.data.has_entry(),
490 "duplicate dirstate entry in read"
490 "duplicate dirstate entry in read"
491 );
491 );
492 assert!(
492 assert!(
493 node.copy_source.is_none(),
493 node.copy_source.is_none(),
494 "duplicate dirstate entry in read"
494 "duplicate dirstate entry in read"
495 );
495 );
496 node.data = NodeData::Entry(*entry);
496 node.data = NodeData::Entry(*entry);
497 node.copy_source = copy_source.map(Cow::Borrowed);
497 node.copy_source = copy_source.map(Cow::Borrowed);
498 map.nodes_with_entry_count += 1;
498 map.nodes_with_entry_count += 1;
499 if copy_source.is_some() {
499 if copy_source.is_some() {
500 map.nodes_with_copy_source_count += 1
500 map.nodes_with_copy_source_count += 1
501 }
501 }
502 Ok(())
502 Ok(())
503 },
503 },
504 )?;
504 )?;
505 let parents = Some(parents.clone());
505 let parents = Some(parents.clone());
506
506
507 Ok((map, parents))
507 Ok((map, parents))
508 }
508 }
509
509
510 /// Assuming dirstate-v2 format, returns whether the next write should
510 /// Assuming dirstate-v2 format, returns whether the next write should
511 /// append to the existing data file that contains `self.on_disk` (true),
511 /// append to the existing data file that contains `self.on_disk` (true),
512 /// or create a new data file from scratch (false).
512 /// or create a new data file from scratch (false).
513 pub(super) fn write_should_append(&self) -> bool {
513 pub(super) fn write_should_append(&self) -> bool {
514 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
514 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
515 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
515 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
516 }
516 }
517
517
518 fn get_node<'tree>(
518 fn get_node<'tree>(
519 &'tree self,
519 &'tree self,
520 path: &HgPath,
520 path: &HgPath,
521 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
521 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
522 let mut children = self.root.as_ref();
522 let mut children = self.root.as_ref();
523 let mut components = path.components();
523 let mut components = path.components();
524 let mut component =
524 let mut component =
525 components.next().expect("expected at least one components");
525 components.next().expect("expected at least one components");
526 loop {
526 loop {
527 if let Some(child) = children.get(component, self.on_disk)? {
527 if let Some(child) = children.get(component, self.on_disk)? {
528 if let Some(next_component) = components.next() {
528 if let Some(next_component) = components.next() {
529 component = next_component;
529 component = next_component;
530 children = child.children(self.on_disk)?;
530 children = child.children(self.on_disk)?;
531 } else {
531 } else {
532 return Ok(Some(child));
532 return Ok(Some(child));
533 }
533 }
534 } else {
534 } else {
535 return Ok(None);
535 return Ok(None);
536 }
536 }
537 }
537 }
538 }
538 }
539
539
540 /// Returns a mutable reference to the node at `path` if it exists
540 /// Returns a mutable reference to the node at `path` if it exists
541 ///
541 ///
542 /// This takes `root` instead of `&mut self` so that callers can mutate
542 /// This takes `root` instead of `&mut self` so that callers can mutate
543 /// other fields while the returned borrow is still valid
543 /// other fields while the returned borrow is still valid
544 fn get_node_mut<'tree>(
544 fn get_node_mut<'tree>(
545 on_disk: &'on_disk [u8],
545 on_disk: &'on_disk [u8],
546 unreachable_bytes: &mut u32,
546 unreachable_bytes: &mut u32,
547 root: &'tree mut ChildNodes<'on_disk>,
547 root: &'tree mut ChildNodes<'on_disk>,
548 path: &HgPath,
548 path: &HgPath,
549 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
549 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
550 let mut children = root;
550 let mut children = root;
551 let mut components = path.components();
551 let mut components = path.components();
552 let mut component =
552 let mut component =
553 components.next().expect("expected at least one components");
553 components.next().expect("expected at least one components");
554 loop {
554 loop {
555 if let Some(child) = children
555 if let Some(child) = children
556 .make_mut(on_disk, unreachable_bytes)?
556 .make_mut(on_disk, unreachable_bytes)?
557 .get_mut(component)
557 .get_mut(component)
558 {
558 {
559 if let Some(next_component) = components.next() {
559 if let Some(next_component) = components.next() {
560 component = next_component;
560 component = next_component;
561 children = &mut child.children;
561 children = &mut child.children;
562 } else {
562 } else {
563 return Ok(Some(child));
563 return Ok(Some(child));
564 }
564 }
565 } else {
565 } else {
566 return Ok(None);
566 return Ok(None);
567 }
567 }
568 }
568 }
569 }
569 }
570
570
571 pub(super) fn get_or_insert<'tree, 'path>(
571 pub(super) fn get_or_insert<'tree, 'path>(
572 &'tree mut self,
572 &'tree mut self,
573 path: &HgPath,
573 path: &HgPath,
574 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
574 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
575 Self::get_or_insert_node(
575 Self::get_or_insert_node(
576 self.on_disk,
576 self.on_disk,
577 &mut self.unreachable_bytes,
577 &mut self.unreachable_bytes,
578 &mut self.root,
578 &mut self.root,
579 path,
579 path,
580 WithBasename::to_cow_owned,
580 WithBasename::to_cow_owned,
581 |_| {},
581 |_| {},
582 )
582 )
583 }
583 }
584
584
585 fn get_or_insert_node<'tree, 'path>(
585 fn get_or_insert_node<'tree, 'path>(
586 on_disk: &'on_disk [u8],
586 on_disk: &'on_disk [u8],
587 unreachable_bytes: &mut u32,
587 unreachable_bytes: &mut u32,
588 root: &'tree mut ChildNodes<'on_disk>,
588 root: &'tree mut ChildNodes<'on_disk>,
589 path: &'path HgPath,
589 path: &'path HgPath,
590 to_cow: impl Fn(
590 to_cow: impl Fn(
591 WithBasename<&'path HgPath>,
591 WithBasename<&'path HgPath>,
592 ) -> WithBasename<Cow<'on_disk, HgPath>>,
592 ) -> WithBasename<Cow<'on_disk, HgPath>>,
593 mut each_ancestor: impl FnMut(&mut Node),
593 mut each_ancestor: impl FnMut(&mut Node),
594 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
594 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
595 let mut child_nodes = root;
595 let mut child_nodes = root;
596 let mut inclusive_ancestor_paths =
596 let mut inclusive_ancestor_paths =
597 WithBasename::inclusive_ancestors_of(path);
597 WithBasename::inclusive_ancestors_of(path);
598 let mut ancestor_path = inclusive_ancestor_paths
598 let mut ancestor_path = inclusive_ancestor_paths
599 .next()
599 .next()
600 .expect("expected at least one inclusive ancestor");
600 .expect("expected at least one inclusive ancestor");
601 loop {
601 loop {
602 let (_, child_node) = child_nodes
602 let (_, child_node) = child_nodes
603 .make_mut(on_disk, unreachable_bytes)?
603 .make_mut(on_disk, unreachable_bytes)?
604 .raw_entry_mut()
604 .raw_entry_mut()
605 .from_key(ancestor_path.base_name())
605 .from_key(ancestor_path.base_name())
606 .or_insert_with(|| (to_cow(ancestor_path), Node::default()));
606 .or_insert_with(|| (to_cow(ancestor_path), Node::default()));
607 if let Some(next) = inclusive_ancestor_paths.next() {
607 if let Some(next) = inclusive_ancestor_paths.next() {
608 each_ancestor(child_node);
608 each_ancestor(child_node);
609 ancestor_path = next;
609 ancestor_path = next;
610 child_nodes = &mut child_node.children;
610 child_nodes = &mut child_node.children;
611 } else {
611 } else {
612 return Ok(child_node);
612 return Ok(child_node);
613 }
613 }
614 }
614 }
615 }
615 }
616
616
617 fn reset_state(
617 fn reset_state(
618 &mut self,
618 &mut self,
619 filename: &HgPath,
619 filename: &HgPath,
620 old_entry_opt: Option<DirstateEntry>,
620 old_entry_opt: Option<DirstateEntry>,
621 wc_tracked: bool,
621 wc_tracked: bool,
622 p1_tracked: bool,
622 p1_tracked: bool,
623 p2_info: bool,
623 p2_info: bool,
624 has_meaningful_mtime: bool,
624 has_meaningful_mtime: bool,
625 parent_file_data_opt: Option<ParentFileData>,
625 parent_file_data_opt: Option<ParentFileData>,
626 ) -> Result<(), DirstateError> {
626 ) -> Result<(), DirstateError> {
627 let (had_entry, was_tracked) = match old_entry_opt {
627 let (had_entry, was_tracked) = match old_entry_opt {
628 Some(old_entry) => (true, old_entry.tracked()),
628 Some(old_entry) => (true, old_entry.tracked()),
629 None => (false, false),
629 None => (false, false),
630 };
630 };
631 let node = Self::get_or_insert_node(
631 let node = Self::get_or_insert_node(
632 self.on_disk,
632 self.on_disk,
633 &mut self.unreachable_bytes,
633 &mut self.unreachable_bytes,
634 &mut self.root,
634 &mut self.root,
635 filename,
635 filename,
636 WithBasename::to_cow_owned,
636 WithBasename::to_cow_owned,
637 |ancestor| {
637 |ancestor| {
638 if !had_entry {
638 if !had_entry {
639 ancestor.descendants_with_entry_count += 1;
639 ancestor.descendants_with_entry_count += 1;
640 }
640 }
641 if was_tracked {
641 if was_tracked {
642 if !wc_tracked {
642 if !wc_tracked {
643 ancestor.tracked_descendants_count = ancestor
643 ancestor.tracked_descendants_count = ancestor
644 .tracked_descendants_count
644 .tracked_descendants_count
645 .checked_sub(1)
645 .checked_sub(1)
646 .expect("tracked count to be >= 0");
646 .expect("tracked count to be >= 0");
647 }
647 }
648 } else {
648 } else {
649 if wc_tracked {
649 if wc_tracked {
650 ancestor.tracked_descendants_count += 1;
650 ancestor.tracked_descendants_count += 1;
651 }
651 }
652 }
652 }
653 },
653 },
654 )?;
654 )?;
655
655
656 let v2_data = if let Some(parent_file_data) = parent_file_data_opt {
656 let v2_data = if let Some(parent_file_data) = parent_file_data_opt {
657 DirstateV2Data {
657 DirstateV2Data {
658 wc_tracked,
658 wc_tracked,
659 p1_tracked,
659 p1_tracked,
660 p2_info,
660 p2_info,
661 mode_size: parent_file_data.mode_size,
661 mode_size: parent_file_data.mode_size,
662 mtime: if has_meaningful_mtime {
662 mtime: if has_meaningful_mtime {
663 parent_file_data.mtime
663 parent_file_data.mtime
664 } else {
664 } else {
665 None
665 None
666 },
666 },
667 ..Default::default()
667 ..Default::default()
668 }
668 }
669 } else {
669 } else {
670 DirstateV2Data {
670 DirstateV2Data {
671 wc_tracked,
671 wc_tracked,
672 p1_tracked,
672 p1_tracked,
673 p2_info,
673 p2_info,
674 ..Default::default()
674 ..Default::default()
675 }
675 }
676 };
676 };
677 if !had_entry {
677 if !had_entry {
678 self.nodes_with_entry_count += 1;
678 self.nodes_with_entry_count += 1;
679 }
679 }
680 node.data = NodeData::Entry(DirstateEntry::from_v2_data(v2_data));
680 node.data = NodeData::Entry(DirstateEntry::from_v2_data(v2_data));
681 Ok(())
681 Ok(())
682 }
682 }
683
683
684 fn set_tracked(
684 fn set_tracked(
685 &mut self,
685 &mut self,
686 filename: &HgPath,
686 filename: &HgPath,
687 old_entry_opt: Option<DirstateEntry>,
687 old_entry_opt: Option<DirstateEntry>,
688 ) -> Result<bool, DirstateV2ParseError> {
688 ) -> Result<bool, DirstateV2ParseError> {
689 let was_tracked = old_entry_opt.map_or(false, |e| e.tracked());
689 let was_tracked = old_entry_opt.map_or(false, |e| e.tracked());
690 let had_entry = old_entry_opt.is_some();
690 let had_entry = old_entry_opt.is_some();
691 let tracked_count_increment = if was_tracked { 0 } else { 1 };
691 let tracked_count_increment = if was_tracked { 0 } else { 1 };
692 let mut new = false;
692 let mut new = false;
693
693
694 let node = Self::get_or_insert_node(
694 let node = Self::get_or_insert_node(
695 self.on_disk,
695 self.on_disk,
696 &mut self.unreachable_bytes,
696 &mut self.unreachable_bytes,
697 &mut self.root,
697 &mut self.root,
698 filename,
698 filename,
699 WithBasename::to_cow_owned,
699 WithBasename::to_cow_owned,
700 |ancestor| {
700 |ancestor| {
701 if !had_entry {
701 if !had_entry {
702 ancestor.descendants_with_entry_count += 1;
702 ancestor.descendants_with_entry_count += 1;
703 }
703 }
704
704
705 ancestor.tracked_descendants_count += tracked_count_increment;
705 ancestor.tracked_descendants_count += tracked_count_increment;
706 },
706 },
707 )?;
707 )?;
708 let new_entry = if let Some(old_entry) = old_entry_opt {
708 let new_entry = if let Some(old_entry) = old_entry_opt {
709 let mut e = old_entry.clone();
709 let mut e = old_entry.clone();
710 if e.tracked() {
710 if e.tracked() {
711 // XXX
711 // XXX
712 // This is probably overkill for more case, but we need this to
712 // This is probably overkill for more case, but we need this to
713 // fully replace the `normallookup` call with `set_tracked`
713 // fully replace the `normallookup` call with `set_tracked`
714 // one. Consider smoothing this in the future.
714 // one. Consider smoothing this in the future.
715 e.set_possibly_dirty();
715 e.set_possibly_dirty();
716 } else {
716 } else {
717 new = true;
717 new = true;
718 e.set_tracked();
718 e.set_tracked();
719 }
719 }
720 e
720 e
721 } else {
721 } else {
722 self.nodes_with_entry_count += 1;
722 self.nodes_with_entry_count += 1;
723 new = true;
723 new = true;
724 DirstateEntry::new_tracked()
724 DirstateEntry::new_tracked()
725 };
725 };
726 node.data = NodeData::Entry(new_entry);
726 node.data = NodeData::Entry(new_entry);
727 Ok(new)
727 Ok(new)
728 }
728 }
729
729
730 /// It is the responsibility of the caller to know that there was an entry
730 /// It is the responsibility of the caller to know that there was an entry
731 /// there before. Does not handle the removal of copy source
731 /// there before. Does not handle the removal of copy source
732 fn set_untracked(
732 fn set_untracked(
733 &mut self,
733 &mut self,
734 filename: &HgPath,
734 filename: &HgPath,
735 old_entry: DirstateEntry,
735 old_entry: DirstateEntry,
736 ) -> Result<(), DirstateV2ParseError> {
736 ) -> Result<(), DirstateV2ParseError> {
737 let node = Self::get_or_insert_node(
737 let node = Self::get_or_insert_node(
738 self.on_disk,
738 self.on_disk,
739 &mut self.unreachable_bytes,
739 &mut self.unreachable_bytes,
740 &mut self.root,
740 &mut self.root,
741 filename,
741 filename,
742 WithBasename::to_cow_owned,
742 WithBasename::to_cow_owned,
743 |ancestor| {
743 |ancestor| {
744 ancestor.tracked_descendants_count = ancestor
744 ancestor.tracked_descendants_count = ancestor
745 .tracked_descendants_count
745 .tracked_descendants_count
746 .checked_sub(1)
746 .checked_sub(1)
747 .expect("tracked_descendants_count should be >= 0");
747 .expect("tracked_descendants_count should be >= 0");
748 },
748 },
749 )?;
749 )?;
750 let mut new_entry = old_entry.clone();
750 let mut new_entry = old_entry.clone();
751 new_entry.set_untracked();
751 new_entry.set_untracked();
752 node.data = NodeData::Entry(new_entry);
752 node.data = NodeData::Entry(new_entry);
753 Ok(())
753 Ok(())
754 }
754 }
755
755
756 fn set_clean(
756 fn set_clean(
757 &mut self,
757 &mut self,
758 filename: &HgPath,
758 filename: &HgPath,
759 old_entry: DirstateEntry,
759 old_entry: DirstateEntry,
760 mode: u32,
760 mode: u32,
761 size: u32,
761 size: u32,
762 mtime: TruncatedTimestamp,
762 mtime: TruncatedTimestamp,
763 ) -> Result<(), DirstateError> {
763 ) -> Result<(), DirstateError> {
764 let node = Self::get_or_insert_node(
764 let node = Self::get_or_insert_node(
765 self.on_disk,
765 self.on_disk,
766 &mut self.unreachable_bytes,
766 &mut self.unreachable_bytes,
767 &mut self.root,
767 &mut self.root,
768 filename,
768 filename,
769 WithBasename::to_cow_owned,
769 WithBasename::to_cow_owned,
770 |ancestor| {
770 |ancestor| {
771 if !old_entry.tracked() {
771 if !old_entry.tracked() {
772 ancestor.tracked_descendants_count += 1;
772 ancestor.tracked_descendants_count += 1;
773 }
773 }
774 },
774 },
775 )?;
775 )?;
776 let mut new_entry = old_entry.clone();
776 let mut new_entry = old_entry.clone();
777 new_entry.set_clean(mode, size, mtime);
777 new_entry.set_clean(mode, size, mtime);
778 node.data = NodeData::Entry(new_entry);
778 node.data = NodeData::Entry(new_entry);
779 Ok(())
779 Ok(())
780 }
780 }
781
781
782 fn set_possibly_dirty(
782 fn set_possibly_dirty(
783 &mut self,
783 &mut self,
784 filename: &HgPath,
784 filename: &HgPath,
785 ) -> Result<(), DirstateError> {
785 ) -> Result<(), DirstateError> {
786 let node = Self::get_or_insert_node(
786 let node = Self::get_or_insert_node(
787 self.on_disk,
787 self.on_disk,
788 &mut self.unreachable_bytes,
788 &mut self.unreachable_bytes,
789 &mut self.root,
789 &mut self.root,
790 filename,
790 filename,
791 WithBasename::to_cow_owned,
791 WithBasename::to_cow_owned,
792 |_ancestor| {},
792 |_ancestor| {},
793 )?;
793 )?;
794 let entry = node.data.as_entry_mut().expect("entry should exist");
794 let entry = node.data.as_entry_mut().expect("entry should exist");
795 entry.set_possibly_dirty();
795 entry.set_possibly_dirty();
796 node.data = NodeData::Entry(*entry);
796 node.data = NodeData::Entry(*entry);
797 Ok(())
797 Ok(())
798 }
798 }
799
799
800 fn iter_nodes<'tree>(
800 fn iter_nodes<'tree>(
801 &'tree self,
801 &'tree self,
802 ) -> impl Iterator<
802 ) -> impl Iterator<
803 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
803 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
804 > + 'tree {
804 > + 'tree {
805 // Depth first tree traversal.
805 // Depth first tree traversal.
806 //
806 //
807 // If we could afford internal iteration and recursion,
807 // If we could afford internal iteration and recursion,
808 // this would look like:
808 // this would look like:
809 //
809 //
810 // ```
810 // ```
811 // fn traverse_children(
811 // fn traverse_children(
812 // children: &ChildNodes,
812 // children: &ChildNodes,
813 // each: &mut impl FnMut(&Node),
813 // each: &mut impl FnMut(&Node),
814 // ) {
814 // ) {
815 // for child in children.values() {
815 // for child in children.values() {
816 // traverse_children(&child.children, each);
816 // traverse_children(&child.children, each);
817 // each(child);
817 // each(child);
818 // }
818 // }
819 // }
819 // }
820 // ```
820 // ```
821 //
821 //
822 // However we want an external iterator and therefore can’t use the
822 // However we want an external iterator and therefore can’t use the
823 // call stack. Use an explicit stack instead:
823 // call stack. Use an explicit stack instead:
824 let mut stack = Vec::new();
824 let mut stack = Vec::new();
825 let mut iter = self.root.as_ref().iter();
825 let mut iter = self.root.as_ref().iter();
826 std::iter::from_fn(move || {
826 std::iter::from_fn(move || {
827 while let Some(child_node) = iter.next() {
827 while let Some(child_node) = iter.next() {
828 let children = match child_node.children(self.on_disk) {
828 let children = match child_node.children(self.on_disk) {
829 Ok(children) => children,
829 Ok(children) => children,
830 Err(error) => return Some(Err(error)),
830 Err(error) => return Some(Err(error)),
831 };
831 };
832 // Pseudo-recursion
832 // Pseudo-recursion
833 let new_iter = children.iter();
833 let new_iter = children.iter();
834 let old_iter = std::mem::replace(&mut iter, new_iter);
834 let old_iter = std::mem::replace(&mut iter, new_iter);
835 stack.push((child_node, old_iter));
835 stack.push((child_node, old_iter));
836 }
836 }
837 // Found the end of a `children.iter()` iterator.
837 // Found the end of a `children.iter()` iterator.
838 if let Some((child_node, next_iter)) = stack.pop() {
838 if let Some((child_node, next_iter)) = stack.pop() {
839 // "Return" from pseudo-recursion by restoring state from the
839 // "Return" from pseudo-recursion by restoring state from the
840 // explicit stack
840 // explicit stack
841 iter = next_iter;
841 iter = next_iter;
842
842
843 Some(Ok(child_node))
843 Some(Ok(child_node))
844 } else {
844 } else {
845 // Reached the bottom of the stack, we’re done
845 // Reached the bottom of the stack, we’re done
846 None
846 None
847 }
847 }
848 })
848 })
849 }
849 }
850
850
851 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
851 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
852 if let Cow::Borrowed(path) = path {
852 if let Cow::Borrowed(path) = path {
853 *unreachable_bytes += path.len() as u32
853 *unreachable_bytes += path.len() as u32
854 }
854 }
855 }
855 }
856 }
856 }
857
857
858 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
858 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
859 ///
859 ///
860 /// The callback is only called for incoming `Ok` values. Errors are passed
860 /// The callback is only called for incoming `Ok` values. Errors are passed
861 /// through as-is. In order to let it use the `?` operator the callback is
861 /// through as-is. In order to let it use the `?` operator the callback is
862 /// expected to return a `Result` of `Option`, instead of an `Option` of
862 /// expected to return a `Result` of `Option`, instead of an `Option` of
863 /// `Result`.
863 /// `Result`.
864 fn filter_map_results<'a, I, F, A, B, E>(
864 fn filter_map_results<'a, I, F, A, B, E>(
865 iter: I,
865 iter: I,
866 f: F,
866 f: F,
867 ) -> impl Iterator<Item = Result<B, E>> + 'a
867 ) -> impl Iterator<Item = Result<B, E>> + 'a
868 where
868 where
869 I: Iterator<Item = Result<A, E>> + 'a,
869 I: Iterator<Item = Result<A, E>> + 'a,
870 F: Fn(A) -> Result<Option<B>, E> + 'a,
870 F: Fn(A) -> Result<Option<B>, E> + 'a,
871 {
871 {
872 iter.filter_map(move |result| match result {
872 iter.filter_map(move |result| match result {
873 Ok(node) => f(node).transpose(),
873 Ok(node) => f(node).transpose(),
874 Err(e) => Some(Err(e)),
874 Err(e) => Some(Err(e)),
875 })
875 })
876 }
876 }
877
877
878 impl OwningDirstateMap {
878 impl OwningDirstateMap {
879 pub fn clear(&mut self) {
879 pub fn clear(&mut self) {
880 self.with_dmap_mut(|map| {
880 self.with_dmap_mut(|map| {
881 map.root = Default::default();
881 map.root = Default::default();
882 map.nodes_with_entry_count = 0;
882 map.nodes_with_entry_count = 0;
883 map.nodes_with_copy_source_count = 0;
883 map.nodes_with_copy_source_count = 0;
884 });
884 });
885 }
885 }
886
886
887 pub fn set_entry(
887 pub fn set_entry(
888 &mut self,
888 &mut self,
889 filename: &HgPath,
889 filename: &HgPath,
890 entry: DirstateEntry,
890 entry: DirstateEntry,
891 ) -> Result<(), DirstateV2ParseError> {
891 ) -> Result<(), DirstateV2ParseError> {
892 self.with_dmap_mut(|map| {
892 self.with_dmap_mut(|map| {
893 map.get_or_insert(&filename)?.data = NodeData::Entry(entry);
893 map.get_or_insert(&filename)?.data = NodeData::Entry(entry);
894 Ok(())
894 Ok(())
895 })
895 })
896 }
896 }
897
897
898 pub fn set_tracked(
898 pub fn set_tracked(
899 &mut self,
899 &mut self,
900 filename: &HgPath,
900 filename: &HgPath,
901 ) -> Result<bool, DirstateV2ParseError> {
901 ) -> Result<bool, DirstateV2ParseError> {
902 let old_entry_opt = self.get(filename)?;
902 let old_entry_opt = self.get(filename)?;
903 self.with_dmap_mut(|map| map.set_tracked(filename, old_entry_opt))
903 self.with_dmap_mut(|map| map.set_tracked(filename, old_entry_opt))
904 }
904 }
905
905
906 pub fn set_untracked(
906 pub fn set_untracked(
907 &mut self,
907 &mut self,
908 filename: &HgPath,
908 filename: &HgPath,
909 ) -> Result<bool, DirstateError> {
909 ) -> Result<bool, DirstateError> {
910 let old_entry_opt = self.get(filename)?;
910 let old_entry_opt = self.get(filename)?;
911 match old_entry_opt {
911 match old_entry_opt {
912 None => Ok(false),
912 None => Ok(false),
913 Some(old_entry) => {
913 Some(old_entry) => {
914 if !old_entry.tracked() {
914 if !old_entry.tracked() {
915 // `DirstateMap::set_untracked` is not a noop if
915 // `DirstateMap::set_untracked` is not a noop if
916 // already not tracked as it will decrement the
916 // already not tracked as it will decrement the
917 // tracked counters while going down.
917 // tracked counters while going down.
918 return Ok(true);
918 return Ok(true);
919 }
919 }
920 if old_entry.added() {
920 if old_entry.added() {
921 // Untracking an "added" entry will just result in a
921 // Untracking an "added" entry will just result in a
922 // worthless entry (and other parts of the code will
922 // worthless entry (and other parts of the code will
923 // complain about it), just drop it entirely.
923 // complain about it), just drop it entirely.
924 self.drop_entry_and_copy_source(filename)?;
924 self.drop_entry_and_copy_source(filename)?;
925 return Ok(true);
925 return Ok(true);
926 }
926 }
927 if !old_entry.p2_info() {
927 if !old_entry.p2_info() {
928 self.copy_map_remove(filename)?;
928 self.copy_map_remove(filename)?;
929 }
929 }
930
930
931 self.with_dmap_mut(|map| {
931 self.with_dmap_mut(|map| {
932 map.set_untracked(filename, old_entry)?;
932 map.set_untracked(filename, old_entry)?;
933 Ok(true)
933 Ok(true)
934 })
934 })
935 }
935 }
936 }
936 }
937 }
937 }
938
938
939 pub fn set_clean(
939 pub fn set_clean(
940 &mut self,
940 &mut self,
941 filename: &HgPath,
941 filename: &HgPath,
942 mode: u32,
942 mode: u32,
943 size: u32,
943 size: u32,
944 mtime: TruncatedTimestamp,
944 mtime: TruncatedTimestamp,
945 ) -> Result<(), DirstateError> {
945 ) -> Result<(), DirstateError> {
946 let old_entry = match self.get(filename)? {
946 let old_entry = match self.get(filename)? {
947 None => {
947 None => {
948 return Err(
948 return Err(
949 DirstateMapError::PathNotFound(filename.into()).into()
949 DirstateMapError::PathNotFound(filename.into()).into()
950 )
950 )
951 }
951 }
952 Some(e) => e,
952 Some(e) => e,
953 };
953 };
954 self.copy_map_remove(filename)?;
954 self.copy_map_remove(filename)?;
955 self.with_dmap_mut(|map| {
955 self.with_dmap_mut(|map| {
956 map.set_clean(filename, old_entry, mode, size, mtime)
956 map.set_clean(filename, old_entry, mode, size, mtime)
957 })
957 })
958 }
958 }
959
959
960 pub fn set_possibly_dirty(
960 pub fn set_possibly_dirty(
961 &mut self,
961 &mut self,
962 filename: &HgPath,
962 filename: &HgPath,
963 ) -> Result<(), DirstateError> {
963 ) -> Result<(), DirstateError> {
964 if self.get(filename)?.is_none() {
964 if self.get(filename)?.is_none() {
965 return Err(DirstateMapError::PathNotFound(filename.into()).into());
965 return Err(DirstateMapError::PathNotFound(filename.into()).into());
966 }
966 }
967 self.with_dmap_mut(|map| map.set_possibly_dirty(filename))
967 self.with_dmap_mut(|map| map.set_possibly_dirty(filename))
968 }
968 }
969
969
970 pub fn reset_state(
970 pub fn reset_state(
971 &mut self,
971 &mut self,
972 filename: &HgPath,
972 filename: &HgPath,
973 wc_tracked: bool,
973 wc_tracked: bool,
974 p1_tracked: bool,
974 p1_tracked: bool,
975 p2_info: bool,
975 p2_info: bool,
976 has_meaningful_mtime: bool,
976 has_meaningful_mtime: bool,
977 parent_file_data_opt: Option<ParentFileData>,
977 parent_file_data_opt: Option<ParentFileData>,
978 ) -> Result<(), DirstateError> {
978 ) -> Result<(), DirstateError> {
979 if !(p1_tracked || p2_info || wc_tracked) {
979 if !(p1_tracked || p2_info || wc_tracked) {
980 self.drop_entry_and_copy_source(filename)?;
980 self.drop_entry_and_copy_source(filename)?;
981 return Ok(());
981 return Ok(());
982 }
982 }
983 self.copy_map_remove(filename)?;
983 self.copy_map_remove(filename)?;
984 let old_entry_opt = self.get(filename)?;
984 let old_entry_opt = self.get(filename)?;
985 self.with_dmap_mut(|map| {
985 self.with_dmap_mut(|map| {
986 map.reset_state(
986 map.reset_state(
987 filename,
987 filename,
988 old_entry_opt,
988 old_entry_opt,
989 wc_tracked,
989 wc_tracked,
990 p1_tracked,
990 p1_tracked,
991 p2_info,
991 p2_info,
992 has_meaningful_mtime,
992 has_meaningful_mtime,
993 parent_file_data_opt,
993 parent_file_data_opt,
994 )
994 )
995 })
995 })
996 }
996 }
997
997
998 pub fn drop_entry_and_copy_source(
998 pub fn drop_entry_and_copy_source(
999 &mut self,
999 &mut self,
1000 filename: &HgPath,
1000 filename: &HgPath,
1001 ) -> Result<(), DirstateError> {
1001 ) -> Result<(), DirstateError> {
1002 let was_tracked = self
1002 let was_tracked = self
1003 .get(filename)?
1003 .get(filename)?
1004 .map_or(false, |e| e.state().is_tracked());
1004 .map_or(false, |e| e.state().is_tracked());
1005 struct Dropped {
1005 struct Dropped {
1006 was_tracked: bool,
1006 was_tracked: bool,
1007 had_entry: bool,
1007 had_entry: bool,
1008 had_copy_source: bool,
1008 had_copy_source: bool,
1009 }
1009 }
1010
1010
1011 /// If this returns `Ok(Some((dropped, removed)))`, then
1011 /// If this returns `Ok(Some((dropped, removed)))`, then
1012 ///
1012 ///
1013 /// * `dropped` is about the leaf node that was at `filename`
1013 /// * `dropped` is about the leaf node that was at `filename`
1014 /// * `removed` is whether this particular level of recursion just
1014 /// * `removed` is whether this particular level of recursion just
1015 /// removed a node in `nodes`.
1015 /// removed a node in `nodes`.
1016 fn recur<'on_disk>(
1016 fn recur<'on_disk>(
1017 on_disk: &'on_disk [u8],
1017 on_disk: &'on_disk [u8],
1018 unreachable_bytes: &mut u32,
1018 unreachable_bytes: &mut u32,
1019 nodes: &mut ChildNodes<'on_disk>,
1019 nodes: &mut ChildNodes<'on_disk>,
1020 path: &HgPath,
1020 path: &HgPath,
1021 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
1021 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
1022 let (first_path_component, rest_of_path) =
1022 let (first_path_component, rest_of_path) =
1023 path.split_first_component();
1023 path.split_first_component();
1024 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
1024 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
1025 let node = if let Some(node) = nodes.get_mut(first_path_component)
1025 let node = if let Some(node) = nodes.get_mut(first_path_component)
1026 {
1026 {
1027 node
1027 node
1028 } else {
1028 } else {
1029 return Ok(None);
1029 return Ok(None);
1030 };
1030 };
1031 let dropped;
1031 let dropped;
1032 if let Some(rest) = rest_of_path {
1032 if let Some(rest) = rest_of_path {
1033 if let Some((d, removed)) = recur(
1033 if let Some((d, removed)) = recur(
1034 on_disk,
1034 on_disk,
1035 unreachable_bytes,
1035 unreachable_bytes,
1036 &mut node.children,
1036 &mut node.children,
1037 rest,
1037 rest,
1038 )? {
1038 )? {
1039 dropped = d;
1039 dropped = d;
1040 if dropped.had_entry {
1040 if dropped.had_entry {
1041 node.descendants_with_entry_count = node
1041 node.descendants_with_entry_count = node
1042 .descendants_with_entry_count
1042 .descendants_with_entry_count
1043 .checked_sub(1)
1043 .checked_sub(1)
1044 .expect(
1044 .expect(
1045 "descendants_with_entry_count should be >= 0",
1045 "descendants_with_entry_count should be >= 0",
1046 );
1046 );
1047 }
1047 }
1048 if dropped.was_tracked {
1048 if dropped.was_tracked {
1049 node.tracked_descendants_count = node
1049 node.tracked_descendants_count = node
1050 .tracked_descendants_count
1050 .tracked_descendants_count
1051 .checked_sub(1)
1051 .checked_sub(1)
1052 .expect(
1052 .expect(
1053 "tracked_descendants_count should be >= 0",
1053 "tracked_descendants_count should be >= 0",
1054 );
1054 );
1055 }
1055 }
1056
1056
1057 // Directory caches must be invalidated when removing a
1057 // Directory caches must be invalidated when removing a
1058 // child node
1058 // child node
1059 if removed {
1059 if removed {
1060 if let NodeData::CachedDirectory { .. } = &node.data {
1060 if let NodeData::CachedDirectory { .. } = &node.data {
1061 node.data = NodeData::None
1061 node.data = NodeData::None
1062 }
1062 }
1063 }
1063 }
1064 } else {
1064 } else {
1065 return Ok(None);
1065 return Ok(None);
1066 }
1066 }
1067 } else {
1067 } else {
1068 let entry = node.data.as_entry();
1068 let entry = node.data.as_entry();
1069 let was_tracked = entry.map_or(false, |entry| entry.tracked());
1069 let was_tracked = entry.map_or(false, |entry| entry.tracked());
1070 let had_entry = entry.is_some();
1070 let had_entry = entry.is_some();
1071 if had_entry {
1071 if had_entry {
1072 node.data = NodeData::None
1072 node.data = NodeData::None
1073 }
1073 }
1074 let mut had_copy_source = false;
1074 let mut had_copy_source = false;
1075 if let Some(source) = &node.copy_source {
1075 if let Some(source) = &node.copy_source {
1076 DirstateMap::count_dropped_path(unreachable_bytes, source);
1076 DirstateMap::count_dropped_path(unreachable_bytes, source);
1077 had_copy_source = true;
1077 had_copy_source = true;
1078 node.copy_source = None
1078 node.copy_source = None
1079 }
1079 }
1080 dropped = Dropped {
1080 dropped = Dropped {
1081 was_tracked,
1081 was_tracked,
1082 had_entry,
1082 had_entry,
1083 had_copy_source,
1083 had_copy_source,
1084 };
1084 };
1085 }
1085 }
1086 // After recursion, for both leaf (rest_of_path is None) nodes and
1086 // After recursion, for both leaf (rest_of_path is None) nodes and
1087 // parent nodes, remove a node if it just became empty.
1087 // parent nodes, remove a node if it just became empty.
1088 let remove = !node.data.has_entry()
1088 let remove = !node.data.has_entry()
1089 && node.copy_source.is_none()
1089 && node.copy_source.is_none()
1090 && node.children.is_empty();
1090 && node.children.is_empty();
1091 if remove {
1091 if remove {
1092 let (key, _) =
1092 let (key, _) =
1093 nodes.remove_entry(first_path_component).unwrap();
1093 nodes.remove_entry(first_path_component).unwrap();
1094 DirstateMap::count_dropped_path(
1094 DirstateMap::count_dropped_path(
1095 unreachable_bytes,
1095 unreachable_bytes,
1096 key.full_path(),
1096 key.full_path(),
1097 )
1097 )
1098 }
1098 }
1099 Ok(Some((dropped, remove)))
1099 Ok(Some((dropped, remove)))
1100 }
1100 }
1101
1101
1102 self.with_dmap_mut(|map| {
1102 self.with_dmap_mut(|map| {
1103 if let Some((dropped, _removed)) = recur(
1103 if let Some((dropped, _removed)) = recur(
1104 map.on_disk,
1104 map.on_disk,
1105 &mut map.unreachable_bytes,
1105 &mut map.unreachable_bytes,
1106 &mut map.root,
1106 &mut map.root,
1107 filename,
1107 filename,
1108 )? {
1108 )? {
1109 if dropped.had_entry {
1109 if dropped.had_entry {
1110 map.nodes_with_entry_count = map
1110 map.nodes_with_entry_count = map
1111 .nodes_with_entry_count
1111 .nodes_with_entry_count
1112 .checked_sub(1)
1112 .checked_sub(1)
1113 .expect("nodes_with_entry_count should be >= 0");
1113 .expect("nodes_with_entry_count should be >= 0");
1114 }
1114 }
1115 if dropped.had_copy_source {
1115 if dropped.had_copy_source {
1116 map.nodes_with_copy_source_count = map
1116 map.nodes_with_copy_source_count = map
1117 .nodes_with_copy_source_count
1117 .nodes_with_copy_source_count
1118 .checked_sub(1)
1118 .checked_sub(1)
1119 .expect("nodes_with_copy_source_count should be >= 0");
1119 .expect("nodes_with_copy_source_count should be >= 0");
1120 }
1120 }
1121 } else {
1121 } else {
1122 debug_assert!(!was_tracked);
1122 debug_assert!(!was_tracked);
1123 }
1123 }
1124 Ok(())
1124 Ok(())
1125 })
1125 })
1126 }
1126 }
1127
1127
1128 pub fn has_tracked_dir(
1128 pub fn has_tracked_dir(
1129 &mut self,
1129 &mut self,
1130 directory: &HgPath,
1130 directory: &HgPath,
1131 ) -> Result<bool, DirstateError> {
1131 ) -> Result<bool, DirstateError> {
1132 self.with_dmap_mut(|map| {
1132 self.with_dmap_mut(|map| {
1133 if let Some(node) = map.get_node(directory)? {
1133 if let Some(node) = map.get_node(directory)? {
1134 // A node without a `DirstateEntry` was created to hold child
1134 // A node without a `DirstateEntry` was created to hold child
1135 // nodes, and is therefore a directory.
1135 // nodes, and is therefore a directory.
1136 let state = node.state()?;
1136 let state = node.state()?;
1137 Ok(state.is_none() && node.tracked_descendants_count() > 0)
1137 Ok(state.is_none() && node.tracked_descendants_count() > 0)
1138 } else {
1138 } else {
1139 Ok(false)
1139 Ok(false)
1140 }
1140 }
1141 })
1141 })
1142 }
1142 }
1143
1143
1144 pub fn has_dir(
1144 pub fn has_dir(
1145 &mut self,
1145 &mut self,
1146 directory: &HgPath,
1146 directory: &HgPath,
1147 ) -> Result<bool, DirstateError> {
1147 ) -> Result<bool, DirstateError> {
1148 self.with_dmap_mut(|map| {
1148 self.with_dmap_mut(|map| {
1149 if let Some(node) = map.get_node(directory)? {
1149 if let Some(node) = map.get_node(directory)? {
1150 // A node without a `DirstateEntry` was created to hold child
1150 // A node without a `DirstateEntry` was created to hold child
1151 // nodes, and is therefore a directory.
1151 // nodes, and is therefore a directory.
1152 let state = node.state()?;
1152 let state = node.state()?;
1153 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
1153 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
1154 } else {
1154 } else {
1155 Ok(false)
1155 Ok(false)
1156 }
1156 }
1157 })
1157 })
1158 }
1158 }
1159
1159
1160 #[timed]
1160 #[timed]
1161 pub fn pack_v1(
1161 pub fn pack_v1(
1162 &self,
1162 &self,
1163 parents: DirstateParents,
1163 parents: DirstateParents,
1164 ) -> Result<Vec<u8>, DirstateError> {
1164 ) -> Result<Vec<u8>, DirstateError> {
1165 let map = self.get_map();
1165 let map = self.get_map();
1166 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1166 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1167 // reallocations
1167 // reallocations
1168 let mut size = parents.as_bytes().len();
1168 let mut size = parents.as_bytes().len();
1169 for node in map.iter_nodes() {
1169 for node in map.iter_nodes() {
1170 let node = node?;
1170 let node = node?;
1171 if node.entry()?.is_some() {
1171 if node.entry()?.is_some() {
1172 size += packed_entry_size(
1172 size += packed_entry_size(
1173 node.full_path(map.on_disk)?,
1173 node.full_path(map.on_disk)?,
1174 node.copy_source(map.on_disk)?,
1174 node.copy_source(map.on_disk)?,
1175 );
1175 );
1176 }
1176 }
1177 }
1177 }
1178
1178
1179 let mut packed = Vec::with_capacity(size);
1179 let mut packed = Vec::with_capacity(size);
1180 packed.extend(parents.as_bytes());
1180 packed.extend(parents.as_bytes());
1181
1181
1182 for node in map.iter_nodes() {
1182 for node in map.iter_nodes() {
1183 let node = node?;
1183 let node = node?;
1184 if let Some(entry) = node.entry()? {
1184 if let Some(entry) = node.entry()? {
1185 pack_entry(
1185 pack_entry(
1186 node.full_path(map.on_disk)?,
1186 node.full_path(map.on_disk)?,
1187 &entry,
1187 &entry,
1188 node.copy_source(map.on_disk)?,
1188 node.copy_source(map.on_disk)?,
1189 &mut packed,
1189 &mut packed,
1190 );
1190 );
1191 }
1191 }
1192 }
1192 }
1193 Ok(packed)
1193 Ok(packed)
1194 }
1194 }
1195
1195
1196 /// Returns new data and metadata together with whether that data should be
1196 /// Returns new data and metadata together with whether that data should be
1197 /// appended to the existing data file whose content is at
1197 /// appended to the existing data file whose content is at
1198 /// `map.on_disk` (true), instead of written to a new data file
1198 /// `map.on_disk` (true), instead of written to a new data file
1199 /// (false).
1199 /// (false).
1200 #[timed]
1200 #[timed]
1201 pub fn pack_v2(
1201 pub fn pack_v2(
1202 &self,
1202 &self,
1203 can_append: bool,
1203 can_append: bool,
1204 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool), DirstateError> {
1204 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool), DirstateError> {
1205 let map = self.get_map();
1205 let map = self.get_map();
1206 on_disk::write(map, can_append)
1206 on_disk::write(map, can_append)
1207 }
1207 }
1208
1208
1209 /// `callback` allows the caller to process and do something with the
1209 /// `callback` allows the caller to process and do something with the
1210 /// results of the status. This is needed to do so efficiently (i.e.
1210 /// results of the status. This is needed to do so efficiently (i.e.
1211 /// without cloning the `DirstateStatus` object with its paths) because
1211 /// without cloning the `DirstateStatus` object with its paths) because
1212 /// we need to borrow from `Self`.
1212 /// we need to borrow from `Self`.
1213 pub fn with_status<R>(
1213 pub fn with_status<R>(
1214 &mut self,
1214 &mut self,
1215 matcher: &(dyn Matcher + Sync),
1215 matcher: &(dyn Matcher + Sync),
1216 root_dir: PathBuf,
1216 root_dir: PathBuf,
1217 ignore_files: Vec<PathBuf>,
1217 ignore_files: Vec<PathBuf>,
1218 options: StatusOptions,
1218 options: StatusOptions,
1219 callback: impl for<'r> FnOnce(
1219 callback: impl for<'r> FnOnce(
1220 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
1220 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
1221 ) -> R,
1221 ) -> R,
1222 ) -> R {
1222 ) -> R {
1223 self.with_dmap_mut(|map| {
1223 self.with_dmap_mut(|map| {
1224 callback(super::status::status(
1224 callback(super::status::status(
1225 map,
1225 map,
1226 matcher,
1226 matcher,
1227 root_dir,
1227 root_dir,
1228 ignore_files,
1228 ignore_files,
1229 options,
1229 options,
1230 ))
1230 ))
1231 })
1231 })
1232 }
1232 }
1233
1233
1234 pub fn copy_map_len(&self) -> usize {
1234 pub fn copy_map_len(&self) -> usize {
1235 let map = self.get_map();
1235 let map = self.get_map();
1236 map.nodes_with_copy_source_count as usize
1236 map.nodes_with_copy_source_count as usize
1237 }
1237 }
1238
1238
1239 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1239 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1240 let map = self.get_map();
1240 let map = self.get_map();
1241 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1241 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1242 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1242 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1243 Some((node.full_path(map.on_disk)?, source))
1243 Some((node.full_path(map.on_disk)?, source))
1244 } else {
1244 } else {
1245 None
1245 None
1246 })
1246 })
1247 }))
1247 }))
1248 }
1248 }
1249
1249
1250 pub fn copy_map_contains_key(
1250 pub fn copy_map_contains_key(
1251 &self,
1251 &self,
1252 key: &HgPath,
1252 key: &HgPath,
1253 ) -> Result<bool, DirstateV2ParseError> {
1253 ) -> Result<bool, DirstateV2ParseError> {
1254 let map = self.get_map();
1254 let map = self.get_map();
1255 Ok(if let Some(node) = map.get_node(key)? {
1255 Ok(if let Some(node) = map.get_node(key)? {
1256 node.has_copy_source()
1256 node.has_copy_source()
1257 } else {
1257 } else {
1258 false
1258 false
1259 })
1259 })
1260 }
1260 }
1261
1261
1262 pub fn copy_map_get(
1262 pub fn copy_map_get(
1263 &self,
1263 &self,
1264 key: &HgPath,
1264 key: &HgPath,
1265 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1265 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1266 let map = self.get_map();
1266 let map = self.get_map();
1267 if let Some(node) = map.get_node(key)? {
1267 if let Some(node) = map.get_node(key)? {
1268 if let Some(source) = node.copy_source(map.on_disk)? {
1268 if let Some(source) = node.copy_source(map.on_disk)? {
1269 return Ok(Some(source));
1269 return Ok(Some(source));
1270 }
1270 }
1271 }
1271 }
1272 Ok(None)
1272 Ok(None)
1273 }
1273 }
1274
1274
1275 pub fn copy_map_remove(
1275 pub fn copy_map_remove(
1276 &mut self,
1276 &mut self,
1277 key: &HgPath,
1277 key: &HgPath,
1278 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1278 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1279 self.with_dmap_mut(|map| {
1279 self.with_dmap_mut(|map| {
1280 let count = &mut map.nodes_with_copy_source_count;
1280 let count = &mut map.nodes_with_copy_source_count;
1281 let unreachable_bytes = &mut map.unreachable_bytes;
1281 let unreachable_bytes = &mut map.unreachable_bytes;
1282 Ok(DirstateMap::get_node_mut(
1282 Ok(DirstateMap::get_node_mut(
1283 map.on_disk,
1283 map.on_disk,
1284 unreachable_bytes,
1284 unreachable_bytes,
1285 &mut map.root,
1285 &mut map.root,
1286 key,
1286 key,
1287 )?
1287 )?
1288 .and_then(|node| {
1288 .and_then(|node| {
1289 if let Some(source) = &node.copy_source {
1289 if let Some(source) = &node.copy_source {
1290 *count -= 1;
1290 *count -= 1;
1291 DirstateMap::count_dropped_path(unreachable_bytes, source);
1291 DirstateMap::count_dropped_path(unreachable_bytes, source);
1292 }
1292 }
1293 node.copy_source.take().map(Cow::into_owned)
1293 node.copy_source.take().map(Cow::into_owned)
1294 }))
1294 }))
1295 })
1295 })
1296 }
1296 }
1297
1297
1298 pub fn copy_map_insert(
1298 pub fn copy_map_insert(
1299 &mut self,
1299 &mut self,
1300 key: HgPathBuf,
1300 key: HgPathBuf,
1301 value: HgPathBuf,
1301 value: HgPathBuf,
1302 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1302 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1303 self.with_dmap_mut(|map| {
1303 self.with_dmap_mut(|map| {
1304 let node = DirstateMap::get_or_insert_node(
1304 let node = DirstateMap::get_or_insert_node(
1305 map.on_disk,
1305 map.on_disk,
1306 &mut map.unreachable_bytes,
1306 &mut map.unreachable_bytes,
1307 &mut map.root,
1307 &mut map.root,
1308 &key,
1308 &key,
1309 WithBasename::to_cow_owned,
1309 WithBasename::to_cow_owned,
1310 |_ancestor| {},
1310 |_ancestor| {},
1311 )?;
1311 )?;
1312 if node.copy_source.is_none() {
1312 if node.copy_source.is_none() {
1313 map.nodes_with_copy_source_count += 1
1313 map.nodes_with_copy_source_count += 1
1314 }
1314 }
1315 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1315 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1316 })
1316 })
1317 }
1317 }
1318
1318
1319 pub fn len(&self) -> usize {
1319 pub fn len(&self) -> usize {
1320 let map = self.get_map();
1320 let map = self.get_map();
1321 map.nodes_with_entry_count as usize
1321 map.nodes_with_entry_count as usize
1322 }
1322 }
1323
1323
1324 pub fn contains_key(
1324 pub fn contains_key(
1325 &self,
1325 &self,
1326 key: &HgPath,
1326 key: &HgPath,
1327 ) -> Result<bool, DirstateV2ParseError> {
1327 ) -> Result<bool, DirstateV2ParseError> {
1328 Ok(self.get(key)?.is_some())
1328 Ok(self.get(key)?.is_some())
1329 }
1329 }
1330
1330
1331 pub fn get(
1331 pub fn get(
1332 &self,
1332 &self,
1333 key: &HgPath,
1333 key: &HgPath,
1334 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1334 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1335 let map = self.get_map();
1335 let map = self.get_map();
1336 Ok(if let Some(node) = map.get_node(key)? {
1336 Ok(if let Some(node) = map.get_node(key)? {
1337 node.entry()?
1337 node.entry()?
1338 } else {
1338 } else {
1339 None
1339 None
1340 })
1340 })
1341 }
1341 }
1342
1342
1343 pub fn iter(&self) -> StateMapIter<'_> {
1343 pub fn iter(&self) -> StateMapIter<'_> {
1344 let map = self.get_map();
1344 let map = self.get_map();
1345 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1345 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1346 Ok(if let Some(entry) = node.entry()? {
1346 Ok(if let Some(entry) = node.entry()? {
1347 Some((node.full_path(map.on_disk)?, entry))
1347 Some((node.full_path(map.on_disk)?, entry))
1348 } else {
1348 } else {
1349 None
1349 None
1350 })
1350 })
1351 }))
1351 }))
1352 }
1352 }
1353
1353
1354 pub fn iter_tracked_dirs(
1354 pub fn iter_tracked_dirs(
1355 &mut self,
1355 &mut self,
1356 ) -> Result<
1356 ) -> Result<
1357 Box<
1357 Box<
1358 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1358 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1359 + Send
1359 + Send
1360 + '_,
1360 + '_,
1361 >,
1361 >,
1362 DirstateError,
1362 DirstateError,
1363 > {
1363 > {
1364 let map = self.get_map();
1364 let map = self.get_map();
1365 let on_disk = map.on_disk;
1365 let on_disk = map.on_disk;
1366 Ok(Box::new(filter_map_results(
1366 Ok(Box::new(filter_map_results(
1367 map.iter_nodes(),
1367 map.iter_nodes(),
1368 move |node| {
1368 move |node| {
1369 Ok(if node.tracked_descendants_count() > 0 {
1369 Ok(if node.tracked_descendants_count() > 0 {
1370 Some(node.full_path(on_disk)?)
1370 Some(node.full_path(on_disk)?)
1371 } else {
1371 } else {
1372 None
1372 None
1373 })
1373 })
1374 },
1374 },
1375 )))
1375 )))
1376 }
1376 }
1377
1377
1378 /// Only public because it needs to be exposed to the Python layer.
1379 /// It is not the full `setparents` logic, only the parts that mutate the
1380 /// entries.
1381 pub fn setparents_fixup(
1382 &mut self,
1383 ) -> Result<Vec<(HgPathBuf, HgPathBuf)>, DirstateV2ParseError> {
1384 // XXX
1385 // All the copying and re-querying is quite inefficient, but this is
1386 // still a lot better than doing it from Python.
1387 //
1388 // The better solution is to develop a mechanism for `iter_mut`,
1389 // which will be a lot more involved: we're dealing with a lazy,
1390 // append-mostly, tree-like data structure. This will do for now.
1391 let mut copies = vec![];
1392 let mut files_with_p2_info = vec![];
1393 for res in self.iter() {
1394 let (path, entry) = res?;
1395 if entry.p2_info() {
1396 files_with_p2_info.push(path.to_owned())
1397 }
1398 }
1399 self.with_dmap_mut(|map| {
1400 for path in files_with_p2_info.iter() {
1401 let node = map.get_or_insert(path)?;
1402 let entry =
1403 node.data.as_entry_mut().expect("entry should exist");
1404 entry.drop_merge_data();
1405 if let Some(source) = node.copy_source.take().as_deref() {
1406 copies.push((path.to_owned(), source.to_owned()));
1407 }
1408 }
1409 Ok(copies)
1410 })
1411 }
1412
1378 pub fn debug_iter(
1413 pub fn debug_iter(
1379 &self,
1414 &self,
1380 all: bool,
1415 all: bool,
1381 ) -> Box<
1416 ) -> Box<
1382 dyn Iterator<
1417 dyn Iterator<
1383 Item = Result<
1418 Item = Result<
1384 (&HgPath, (u8, i32, i32, i32)),
1419 (&HgPath, (u8, i32, i32, i32)),
1385 DirstateV2ParseError,
1420 DirstateV2ParseError,
1386 >,
1421 >,
1387 > + Send
1422 > + Send
1388 + '_,
1423 + '_,
1389 > {
1424 > {
1390 let map = self.get_map();
1425 let map = self.get_map();
1391 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1426 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1392 let debug_tuple = if let Some(entry) = node.entry()? {
1427 let debug_tuple = if let Some(entry) = node.entry()? {
1393 entry.debug_tuple()
1428 entry.debug_tuple()
1394 } else if !all {
1429 } else if !all {
1395 return Ok(None);
1430 return Ok(None);
1396 } else if let Some(mtime) = node.cached_directory_mtime()? {
1431 } else if let Some(mtime) = node.cached_directory_mtime()? {
1397 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1432 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1398 } else {
1433 } else {
1399 (b' ', 0, -1, -1)
1434 (b' ', 0, -1, -1)
1400 };
1435 };
1401 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1436 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1402 }))
1437 }))
1403 }
1438 }
1404 }
1439 }
@@ -1,559 +1,572 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,
26 dirstate::StateMapIter,
27 dirstate_tree::on_disk::DirstateV2ParseError,
27 dirstate_tree::on_disk::DirstateV2ParseError,
28 dirstate_tree::owning::OwningDirstateMap,
28 dirstate_tree::owning::OwningDirstateMap,
29 revlog::Node,
29 revlog::Node,
30 utils::files::normalize_case,
30 utils::files::normalize_case,
31 utils::hg_path::{HgPath, HgPathBuf},
31 utils::hg_path::{HgPath, HgPathBuf},
32 DirstateEntry, DirstateError, DirstateParents, EntryState,
32 DirstateEntry, DirstateError, DirstateParents, EntryState,
33 };
33 };
34
34
35 // TODO
35 // TODO
36 // This object needs to share references to multiple members of its Rust
36 // This object needs to share references to multiple members of its Rust
37 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
37 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
38 // Right now `CopyMap` is done, but it needs to have an explicit reference
38 // Right now `CopyMap` is done, but it needs to have an explicit reference
39 // to `RustDirstateMap` which itself needs to have an encapsulation for
39 // to `RustDirstateMap` which itself needs to have an encapsulation for
40 // every method in `CopyMap` (copymapcopy, etc.).
40 // every method in `CopyMap` (copymapcopy, etc.).
41 // This is ugly and hard to maintain.
41 // This is ugly and hard to maintain.
42 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
42 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
43 // `py_class!` is already implemented and does not mention
43 // `py_class!` is already implemented and does not mention
44 // `RustDirstateMap`, rightfully so.
44 // `RustDirstateMap`, rightfully so.
45 // All attributes also have to have a separate refcount data attribute for
45 // All attributes also have to have a separate refcount data attribute for
46 // leaks, with all methods that go along for reference sharing.
46 // leaks, with all methods that go along for reference sharing.
47 py_class!(pub class DirstateMap |py| {
47 py_class!(pub class DirstateMap |py| {
48 @shared data inner: OwningDirstateMap;
48 @shared data inner: OwningDirstateMap;
49
49
50 /// Returns a `(dirstate_map, parents)` tuple
50 /// Returns a `(dirstate_map, parents)` tuple
51 @staticmethod
51 @staticmethod
52 def new_v1(
52 def new_v1(
53 on_disk: PyBytes,
53 on_disk: PyBytes,
54 ) -> PyResult<PyObject> {
54 ) -> PyResult<PyObject> {
55 let on_disk = PyBytesDeref::new(py, on_disk);
55 let on_disk = PyBytesDeref::new(py, on_disk);
56 let (map, parents) = OwningDirstateMap::new_v1(on_disk)
56 let (map, parents) = OwningDirstateMap::new_v1(on_disk)
57 .map_err(|e| dirstate_error(py, e))?;
57 .map_err(|e| dirstate_error(py, e))?;
58 let map = Self::create_instance(py, map)?;
58 let map = Self::create_instance(py, map)?;
59 let p1 = PyBytes::new(py, parents.p1.as_bytes());
59 let p1 = PyBytes::new(py, parents.p1.as_bytes());
60 let p2 = PyBytes::new(py, parents.p2.as_bytes());
60 let p2 = PyBytes::new(py, parents.p2.as_bytes());
61 let parents = (p1, p2);
61 let parents = (p1, p2);
62 Ok((map, parents).to_py_object(py).into_object())
62 Ok((map, parents).to_py_object(py).into_object())
63 }
63 }
64
64
65 /// Returns a DirstateMap
65 /// Returns a DirstateMap
66 @staticmethod
66 @staticmethod
67 def new_v2(
67 def new_v2(
68 on_disk: PyBytes,
68 on_disk: PyBytes,
69 data_size: usize,
69 data_size: usize,
70 tree_metadata: PyBytes,
70 tree_metadata: PyBytes,
71 ) -> PyResult<PyObject> {
71 ) -> PyResult<PyObject> {
72 let dirstate_error = |e: DirstateError| {
72 let dirstate_error = |e: DirstateError| {
73 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
73 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
74 };
74 };
75 let on_disk = PyBytesDeref::new(py, on_disk);
75 let on_disk = PyBytesDeref::new(py, on_disk);
76 let map = OwningDirstateMap::new_v2(
76 let map = OwningDirstateMap::new_v2(
77 on_disk, data_size, tree_metadata.data(py),
77 on_disk, data_size, tree_metadata.data(py),
78 ).map_err(dirstate_error)?;
78 ).map_err(dirstate_error)?;
79 let map = Self::create_instance(py, map)?;
79 let map = Self::create_instance(py, map)?;
80 Ok(map.into_object())
80 Ok(map.into_object())
81 }
81 }
82
82
83 def clear(&self) -> PyResult<PyObject> {
83 def clear(&self) -> PyResult<PyObject> {
84 self.inner(py).borrow_mut().clear();
84 self.inner(py).borrow_mut().clear();
85 Ok(py.None())
85 Ok(py.None())
86 }
86 }
87
87
88 def get(
88 def get(
89 &self,
89 &self,
90 key: PyObject,
90 key: PyObject,
91 default: Option<PyObject> = None
91 default: Option<PyObject> = None
92 ) -> PyResult<Option<PyObject>> {
92 ) -> PyResult<Option<PyObject>> {
93 let key = key.extract::<PyBytes>(py)?;
93 let key = key.extract::<PyBytes>(py)?;
94 match self
94 match self
95 .inner(py)
95 .inner(py)
96 .borrow()
96 .borrow()
97 .get(HgPath::new(key.data(py)))
97 .get(HgPath::new(key.data(py)))
98 .map_err(|e| v2_error(py, e))?
98 .map_err(|e| v2_error(py, e))?
99 {
99 {
100 Some(entry) => {
100 Some(entry) => {
101 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
101 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
102 },
102 },
103 None => Ok(default)
103 None => Ok(default)
104 }
104 }
105 }
105 }
106
106
107 def set_dirstate_item(
107 def set_dirstate_item(
108 &self,
108 &self,
109 path: PyObject,
109 path: PyObject,
110 item: DirstateItem
110 item: DirstateItem
111 ) -> PyResult<PyObject> {
111 ) -> PyResult<PyObject> {
112 let f = path.extract::<PyBytes>(py)?;
112 let f = path.extract::<PyBytes>(py)?;
113 let filename = HgPath::new(f.data(py));
113 let filename = HgPath::new(f.data(py));
114 self.inner(py)
114 self.inner(py)
115 .borrow_mut()
115 .borrow_mut()
116 .set_entry(filename, item.get_entry(py))
116 .set_entry(filename, item.get_entry(py))
117 .map_err(|e| v2_error(py, e))?;
117 .map_err(|e| v2_error(py, e))?;
118 Ok(py.None())
118 Ok(py.None())
119 }
119 }
120
120
121 def set_tracked(&self, f: PyObject) -> PyResult<PyBool> {
121 def set_tracked(&self, f: PyObject) -> PyResult<PyBool> {
122 let bytes = f.extract::<PyBytes>(py)?;
122 let bytes = f.extract::<PyBytes>(py)?;
123 let path = HgPath::new(bytes.data(py));
123 let path = HgPath::new(bytes.data(py));
124 let res = self.inner(py).borrow_mut().set_tracked(path);
124 let res = self.inner(py).borrow_mut().set_tracked(path);
125 let was_tracked = res.or_else(|_| {
125 let was_tracked = res.or_else(|_| {
126 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
126 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
127 })?;
127 })?;
128 Ok(was_tracked.to_py_object(py))
128 Ok(was_tracked.to_py_object(py))
129 }
129 }
130
130
131 def set_untracked(&self, f: PyObject) -> PyResult<PyBool> {
131 def set_untracked(&self, f: PyObject) -> PyResult<PyBool> {
132 let bytes = f.extract::<PyBytes>(py)?;
132 let bytes = f.extract::<PyBytes>(py)?;
133 let path = HgPath::new(bytes.data(py));
133 let path = HgPath::new(bytes.data(py));
134 let res = self.inner(py).borrow_mut().set_untracked(path);
134 let res = self.inner(py).borrow_mut().set_untracked(path);
135 let was_tracked = res.or_else(|_| {
135 let was_tracked = res.or_else(|_| {
136 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
136 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
137 })?;
137 })?;
138 Ok(was_tracked.to_py_object(py))
138 Ok(was_tracked.to_py_object(py))
139 }
139 }
140
140
141 def set_clean(
141 def set_clean(
142 &self,
142 &self,
143 f: PyObject,
143 f: PyObject,
144 mode: u32,
144 mode: u32,
145 size: u32,
145 size: u32,
146 mtime: (i64, u32, bool)
146 mtime: (i64, u32, bool)
147 ) -> PyResult<PyNone> {
147 ) -> PyResult<PyNone> {
148 let (mtime_s, mtime_ns, second_ambiguous) = mtime;
148 let (mtime_s, mtime_ns, second_ambiguous) = mtime;
149 let timestamp = TruncatedTimestamp::new_truncate(
149 let timestamp = TruncatedTimestamp::new_truncate(
150 mtime_s, mtime_ns, second_ambiguous
150 mtime_s, mtime_ns, second_ambiguous
151 );
151 );
152 let bytes = f.extract::<PyBytes>(py)?;
152 let bytes = f.extract::<PyBytes>(py)?;
153 let path = HgPath::new(bytes.data(py));
153 let path = HgPath::new(bytes.data(py));
154 let res = self.inner(py).borrow_mut().set_clean(
154 let res = self.inner(py).borrow_mut().set_clean(
155 path, mode, size, timestamp,
155 path, mode, size, timestamp,
156 );
156 );
157 res.or_else(|_| {
157 res.or_else(|_| {
158 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
158 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
159 })?;
159 })?;
160 Ok(PyNone)
160 Ok(PyNone)
161 }
161 }
162
162
163 def set_possibly_dirty(&self, f: PyObject) -> PyResult<PyNone> {
163 def set_possibly_dirty(&self, f: PyObject) -> PyResult<PyNone> {
164 let bytes = f.extract::<PyBytes>(py)?;
164 let bytes = f.extract::<PyBytes>(py)?;
165 let path = HgPath::new(bytes.data(py));
165 let path = HgPath::new(bytes.data(py));
166 let res = self.inner(py).borrow_mut().set_possibly_dirty(path);
166 let res = self.inner(py).borrow_mut().set_possibly_dirty(path);
167 res.or_else(|_| {
167 res.or_else(|_| {
168 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
168 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
169 })?;
169 })?;
170 Ok(PyNone)
170 Ok(PyNone)
171 }
171 }
172
172
173 def reset_state(
173 def reset_state(
174 &self,
174 &self,
175 f: PyObject,
175 f: PyObject,
176 wc_tracked: bool,
176 wc_tracked: bool,
177 p1_tracked: bool,
177 p1_tracked: bool,
178 p2_info: bool,
178 p2_info: bool,
179 has_meaningful_mtime: bool,
179 has_meaningful_mtime: bool,
180 parentfiledata: Option<(u32, u32, Option<(i64, u32, bool)>)>,
180 parentfiledata: Option<(u32, u32, Option<(i64, u32, bool)>)>,
181 ) -> PyResult<PyNone> {
181 ) -> PyResult<PyNone> {
182 let mut has_meaningful_mtime = has_meaningful_mtime;
182 let mut has_meaningful_mtime = has_meaningful_mtime;
183 let parent_file_data = match parentfiledata {
183 let parent_file_data = match parentfiledata {
184 None => {
184 None => {
185 has_meaningful_mtime = false;
185 has_meaningful_mtime = false;
186 None
186 None
187 },
187 },
188 Some(data) => {
188 Some(data) => {
189 let (mode, size, mtime_info) = data;
189 let (mode, size, mtime_info) = data;
190 let mtime = if let Some(mtime_info) = mtime_info {
190 let mtime = if let Some(mtime_info) = mtime_info {
191 let (mtime_s, mtime_ns, second_ambiguous) = mtime_info;
191 let (mtime_s, mtime_ns, second_ambiguous) = mtime_info;
192 let timestamp = TruncatedTimestamp::new_truncate(
192 let timestamp = TruncatedTimestamp::new_truncate(
193 mtime_s, mtime_ns, second_ambiguous
193 mtime_s, mtime_ns, second_ambiguous
194 );
194 );
195 Some(timestamp)
195 Some(timestamp)
196 } else {
196 } else {
197 has_meaningful_mtime = false;
197 has_meaningful_mtime = false;
198 None
198 None
199 };
199 };
200 Some(ParentFileData {
200 Some(ParentFileData {
201 mode_size: Some((mode, size)),
201 mode_size: Some((mode, size)),
202 mtime,
202 mtime,
203 })
203 })
204 }
204 }
205 };
205 };
206 let bytes = f.extract::<PyBytes>(py)?;
206 let bytes = f.extract::<PyBytes>(py)?;
207 let path = HgPath::new(bytes.data(py));
207 let path = HgPath::new(bytes.data(py));
208 let res = self.inner(py).borrow_mut().reset_state(
208 let res = self.inner(py).borrow_mut().reset_state(
209 path,
209 path,
210 wc_tracked,
210 wc_tracked,
211 p1_tracked,
211 p1_tracked,
212 p2_info,
212 p2_info,
213 has_meaningful_mtime,
213 has_meaningful_mtime,
214 parent_file_data,
214 parent_file_data,
215 );
215 );
216 res.or_else(|_| {
216 res.or_else(|_| {
217 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
217 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
218 })?;
218 })?;
219 Ok(PyNone)
219 Ok(PyNone)
220 }
220 }
221
221
222 def drop_item_and_copy_source(
222 def drop_item_and_copy_source(
223 &self,
223 &self,
224 f: PyBytes,
224 f: PyBytes,
225 ) -> PyResult<PyNone> {
225 ) -> PyResult<PyNone> {
226 self.inner(py)
226 self.inner(py)
227 .borrow_mut()
227 .borrow_mut()
228 .drop_entry_and_copy_source(HgPath::new(f.data(py)))
228 .drop_entry_and_copy_source(HgPath::new(f.data(py)))
229 .map_err(|e |dirstate_error(py, e))?;
229 .map_err(|e |dirstate_error(py, e))?;
230 Ok(PyNone)
230 Ok(PyNone)
231 }
231 }
232
232
233 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
233 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
234 let d = d.extract::<PyBytes>(py)?;
234 let d = d.extract::<PyBytes>(py)?;
235 Ok(self.inner(py).borrow_mut()
235 Ok(self.inner(py).borrow_mut()
236 .has_tracked_dir(HgPath::new(d.data(py)))
236 .has_tracked_dir(HgPath::new(d.data(py)))
237 .map_err(|e| {
237 .map_err(|e| {
238 PyErr::new::<exc::ValueError, _>(py, e.to_string())
238 PyErr::new::<exc::ValueError, _>(py, e.to_string())
239 })?
239 })?
240 .to_py_object(py))
240 .to_py_object(py))
241 }
241 }
242
242
243 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
243 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
244 let d = d.extract::<PyBytes>(py)?;
244 let d = d.extract::<PyBytes>(py)?;
245 Ok(self.inner(py).borrow_mut()
245 Ok(self.inner(py).borrow_mut()
246 .has_dir(HgPath::new(d.data(py)))
246 .has_dir(HgPath::new(d.data(py)))
247 .map_err(|e| {
247 .map_err(|e| {
248 PyErr::new::<exc::ValueError, _>(py, e.to_string())
248 PyErr::new::<exc::ValueError, _>(py, e.to_string())
249 })?
249 })?
250 .to_py_object(py))
250 .to_py_object(py))
251 }
251 }
252
252
253 def write_v1(
253 def write_v1(
254 &self,
254 &self,
255 p1: PyObject,
255 p1: PyObject,
256 p2: PyObject,
256 p2: PyObject,
257 ) -> PyResult<PyBytes> {
257 ) -> PyResult<PyBytes> {
258 let inner = self.inner(py).borrow();
258 let inner = self.inner(py).borrow();
259 let parents = DirstateParents {
259 let parents = DirstateParents {
260 p1: extract_node_id(py, &p1)?,
260 p1: extract_node_id(py, &p1)?,
261 p2: extract_node_id(py, &p2)?,
261 p2: extract_node_id(py, &p2)?,
262 };
262 };
263 let result = inner.pack_v1(parents);
263 let result = inner.pack_v1(parents);
264 match result {
264 match result {
265 Ok(packed) => Ok(PyBytes::new(py, &packed)),
265 Ok(packed) => Ok(PyBytes::new(py, &packed)),
266 Err(_) => Err(PyErr::new::<exc::OSError, _>(
266 Err(_) => Err(PyErr::new::<exc::OSError, _>(
267 py,
267 py,
268 "Dirstate error".to_string(),
268 "Dirstate error".to_string(),
269 )),
269 )),
270 }
270 }
271 }
271 }
272
272
273 /// Returns new data together with whether that data should be appended to
273 /// Returns new data together with whether that data should be appended to
274 /// the existing data file whose content is at `self.on_disk` (True),
274 /// the existing data file whose content is at `self.on_disk` (True),
275 /// instead of written to a new data file (False).
275 /// instead of written to a new data file (False).
276 def write_v2(
276 def write_v2(
277 &self,
277 &self,
278 can_append: bool,
278 can_append: bool,
279 ) -> PyResult<PyObject> {
279 ) -> PyResult<PyObject> {
280 let inner = self.inner(py).borrow();
280 let inner = self.inner(py).borrow();
281 let result = inner.pack_v2(can_append);
281 let result = inner.pack_v2(can_append);
282 match result {
282 match result {
283 Ok((packed, tree_metadata, append)) => {
283 Ok((packed, tree_metadata, append)) => {
284 let packed = PyBytes::new(py, &packed);
284 let packed = PyBytes::new(py, &packed);
285 let tree_metadata = PyBytes::new(py, tree_metadata.as_bytes());
285 let tree_metadata = PyBytes::new(py, tree_metadata.as_bytes());
286 let tuple = (packed, tree_metadata, append);
286 let tuple = (packed, tree_metadata, append);
287 Ok(tuple.to_py_object(py).into_object())
287 Ok(tuple.to_py_object(py).into_object())
288 },
288 },
289 Err(_) => Err(PyErr::new::<exc::OSError, _>(
289 Err(_) => Err(PyErr::new::<exc::OSError, _>(
290 py,
290 py,
291 "Dirstate error".to_string(),
291 "Dirstate error".to_string(),
292 )),
292 )),
293 }
293 }
294 }
294 }
295
295
296 def filefoldmapasdict(&self) -> PyResult<PyDict> {
296 def filefoldmapasdict(&self) -> PyResult<PyDict> {
297 let dict = PyDict::new(py);
297 let dict = PyDict::new(py);
298 for item in self.inner(py).borrow_mut().iter() {
298 for item in self.inner(py).borrow_mut().iter() {
299 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
299 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
300 if entry.state() != EntryState::Removed {
300 if entry.state() != EntryState::Removed {
301 let key = normalize_case(path);
301 let key = normalize_case(path);
302 let value = path;
302 let value = path;
303 dict.set_item(
303 dict.set_item(
304 py,
304 py,
305 PyBytes::new(py, key.as_bytes()).into_object(),
305 PyBytes::new(py, key.as_bytes()).into_object(),
306 PyBytes::new(py, value.as_bytes()).into_object(),
306 PyBytes::new(py, value.as_bytes()).into_object(),
307 )?;
307 )?;
308 }
308 }
309 }
309 }
310 Ok(dict)
310 Ok(dict)
311 }
311 }
312
312
313 def __len__(&self) -> PyResult<usize> {
313 def __len__(&self) -> PyResult<usize> {
314 Ok(self.inner(py).borrow().len())
314 Ok(self.inner(py).borrow().len())
315 }
315 }
316
316
317 def __contains__(&self, key: PyObject) -> PyResult<bool> {
317 def __contains__(&self, key: PyObject) -> PyResult<bool> {
318 let key = key.extract::<PyBytes>(py)?;
318 let key = key.extract::<PyBytes>(py)?;
319 self.inner(py)
319 self.inner(py)
320 .borrow()
320 .borrow()
321 .contains_key(HgPath::new(key.data(py)))
321 .contains_key(HgPath::new(key.data(py)))
322 .map_err(|e| v2_error(py, e))
322 .map_err(|e| v2_error(py, e))
323 }
323 }
324
324
325 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
325 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
326 let key = key.extract::<PyBytes>(py)?;
326 let key = key.extract::<PyBytes>(py)?;
327 let key = HgPath::new(key.data(py));
327 let key = HgPath::new(key.data(py));
328 match self
328 match self
329 .inner(py)
329 .inner(py)
330 .borrow()
330 .borrow()
331 .get(key)
331 .get(key)
332 .map_err(|e| v2_error(py, e))?
332 .map_err(|e| v2_error(py, e))?
333 {
333 {
334 Some(entry) => {
334 Some(entry) => {
335 Ok(DirstateItem::new_as_pyobject(py, entry)?)
335 Ok(DirstateItem::new_as_pyobject(py, entry)?)
336 },
336 },
337 None => Err(PyErr::new::<exc::KeyError, _>(
337 None => Err(PyErr::new::<exc::KeyError, _>(
338 py,
338 py,
339 String::from_utf8_lossy(key.as_bytes()),
339 String::from_utf8_lossy(key.as_bytes()),
340 )),
340 )),
341 }
341 }
342 }
342 }
343
343
344 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
344 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
345 let leaked_ref = self.inner(py).leak_immutable();
345 let leaked_ref = self.inner(py).leak_immutable();
346 DirstateMapKeysIterator::from_inner(
346 DirstateMapKeysIterator::from_inner(
347 py,
347 py,
348 unsafe { leaked_ref.map(py, |o| o.iter()) },
348 unsafe { leaked_ref.map(py, |o| o.iter()) },
349 )
349 )
350 }
350 }
351
351
352 def items(&self) -> PyResult<DirstateMapItemsIterator> {
352 def items(&self) -> PyResult<DirstateMapItemsIterator> {
353 let leaked_ref = self.inner(py).leak_immutable();
353 let leaked_ref = self.inner(py).leak_immutable();
354 DirstateMapItemsIterator::from_inner(
354 DirstateMapItemsIterator::from_inner(
355 py,
355 py,
356 unsafe { leaked_ref.map(py, |o| o.iter()) },
356 unsafe { leaked_ref.map(py, |o| o.iter()) },
357 )
357 )
358 }
358 }
359
359
360 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
360 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
361 let leaked_ref = self.inner(py).leak_immutable();
361 let leaked_ref = self.inner(py).leak_immutable();
362 DirstateMapKeysIterator::from_inner(
362 DirstateMapKeysIterator::from_inner(
363 py,
363 py,
364 unsafe { leaked_ref.map(py, |o| o.iter()) },
364 unsafe { leaked_ref.map(py, |o| o.iter()) },
365 )
365 )
366 }
366 }
367
367
368 // TODO all copymap* methods, see docstring above
368 // TODO all copymap* methods, see docstring above
369 def copymapcopy(&self) -> PyResult<PyDict> {
369 def copymapcopy(&self) -> PyResult<PyDict> {
370 let dict = PyDict::new(py);
370 let dict = PyDict::new(py);
371 for item in self.inner(py).borrow().copy_map_iter() {
371 for item in self.inner(py).borrow().copy_map_iter() {
372 let (key, value) = item.map_err(|e| v2_error(py, e))?;
372 let (key, value) = item.map_err(|e| v2_error(py, e))?;
373 dict.set_item(
373 dict.set_item(
374 py,
374 py,
375 PyBytes::new(py, key.as_bytes()),
375 PyBytes::new(py, key.as_bytes()),
376 PyBytes::new(py, value.as_bytes()),
376 PyBytes::new(py, value.as_bytes()),
377 )?;
377 )?;
378 }
378 }
379 Ok(dict)
379 Ok(dict)
380 }
380 }
381
381
382 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
382 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
383 let key = key.extract::<PyBytes>(py)?;
383 let key = key.extract::<PyBytes>(py)?;
384 match self
384 match self
385 .inner(py)
385 .inner(py)
386 .borrow()
386 .borrow()
387 .copy_map_get(HgPath::new(key.data(py)))
387 .copy_map_get(HgPath::new(key.data(py)))
388 .map_err(|e| v2_error(py, e))?
388 .map_err(|e| v2_error(py, e))?
389 {
389 {
390 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
390 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
391 None => Err(PyErr::new::<exc::KeyError, _>(
391 None => Err(PyErr::new::<exc::KeyError, _>(
392 py,
392 py,
393 String::from_utf8_lossy(key.data(py)),
393 String::from_utf8_lossy(key.data(py)),
394 )),
394 )),
395 }
395 }
396 }
396 }
397 def copymap(&self) -> PyResult<CopyMap> {
397 def copymap(&self) -> PyResult<CopyMap> {
398 CopyMap::from_inner(py, self.clone_ref(py))
398 CopyMap::from_inner(py, self.clone_ref(py))
399 }
399 }
400
400
401 def copymaplen(&self) -> PyResult<usize> {
401 def copymaplen(&self) -> PyResult<usize> {
402 Ok(self.inner(py).borrow().copy_map_len())
402 Ok(self.inner(py).borrow().copy_map_len())
403 }
403 }
404 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
404 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
405 let key = key.extract::<PyBytes>(py)?;
405 let key = key.extract::<PyBytes>(py)?;
406 self.inner(py)
406 self.inner(py)
407 .borrow()
407 .borrow()
408 .copy_map_contains_key(HgPath::new(key.data(py)))
408 .copy_map_contains_key(HgPath::new(key.data(py)))
409 .map_err(|e| v2_error(py, e))
409 .map_err(|e| v2_error(py, e))
410 }
410 }
411 def copymapget(
411 def copymapget(
412 &self,
412 &self,
413 key: PyObject,
413 key: PyObject,
414 default: Option<PyObject>
414 default: Option<PyObject>
415 ) -> PyResult<Option<PyObject>> {
415 ) -> PyResult<Option<PyObject>> {
416 let key = key.extract::<PyBytes>(py)?;
416 let key = key.extract::<PyBytes>(py)?;
417 match self
417 match self
418 .inner(py)
418 .inner(py)
419 .borrow()
419 .borrow()
420 .copy_map_get(HgPath::new(key.data(py)))
420 .copy_map_get(HgPath::new(key.data(py)))
421 .map_err(|e| v2_error(py, e))?
421 .map_err(|e| v2_error(py, e))?
422 {
422 {
423 Some(copy) => Ok(Some(
423 Some(copy) => Ok(Some(
424 PyBytes::new(py, copy.as_bytes()).into_object(),
424 PyBytes::new(py, copy.as_bytes()).into_object(),
425 )),
425 )),
426 None => Ok(default),
426 None => Ok(default),
427 }
427 }
428 }
428 }
429 def copymapsetitem(
429 def copymapsetitem(
430 &self,
430 &self,
431 key: PyObject,
431 key: PyObject,
432 value: PyObject
432 value: PyObject
433 ) -> PyResult<PyObject> {
433 ) -> PyResult<PyObject> {
434 let key = key.extract::<PyBytes>(py)?;
434 let key = key.extract::<PyBytes>(py)?;
435 let value = value.extract::<PyBytes>(py)?;
435 let value = value.extract::<PyBytes>(py)?;
436 self.inner(py)
436 self.inner(py)
437 .borrow_mut()
437 .borrow_mut()
438 .copy_map_insert(
438 .copy_map_insert(
439 HgPathBuf::from_bytes(key.data(py)),
439 HgPathBuf::from_bytes(key.data(py)),
440 HgPathBuf::from_bytes(value.data(py)),
440 HgPathBuf::from_bytes(value.data(py)),
441 )
441 )
442 .map_err(|e| v2_error(py, e))?;
442 .map_err(|e| v2_error(py, e))?;
443 Ok(py.None())
443 Ok(py.None())
444 }
444 }
445 def copymappop(
445 def copymappop(
446 &self,
446 &self,
447 key: PyObject,
447 key: PyObject,
448 default: Option<PyObject>
448 default: Option<PyObject>
449 ) -> PyResult<Option<PyObject>> {
449 ) -> PyResult<Option<PyObject>> {
450 let key = key.extract::<PyBytes>(py)?;
450 let key = key.extract::<PyBytes>(py)?;
451 match self
451 match self
452 .inner(py)
452 .inner(py)
453 .borrow_mut()
453 .borrow_mut()
454 .copy_map_remove(HgPath::new(key.data(py)))
454 .copy_map_remove(HgPath::new(key.data(py)))
455 .map_err(|e| v2_error(py, e))?
455 .map_err(|e| v2_error(py, e))?
456 {
456 {
457 Some(copy) => Ok(Some(
457 Some(copy) => Ok(Some(
458 PyBytes::new(py, copy.as_bytes()).into_object(),
458 PyBytes::new(py, copy.as_bytes()).into_object(),
459 )),
459 )),
460 None => Ok(default),
460 None => Ok(default),
461 }
461 }
462 }
462 }
463
463
464 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
464 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
465 let leaked_ref = self.inner(py).leak_immutable();
465 let leaked_ref = self.inner(py).leak_immutable();
466 CopyMapKeysIterator::from_inner(
466 CopyMapKeysIterator::from_inner(
467 py,
467 py,
468 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
468 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
469 )
469 )
470 }
470 }
471
471
472 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
472 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
473 let leaked_ref = self.inner(py).leak_immutable();
473 let leaked_ref = self.inner(py).leak_immutable();
474 CopyMapItemsIterator::from_inner(
474 CopyMapItemsIterator::from_inner(
475 py,
475 py,
476 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
476 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
477 )
477 )
478 }
478 }
479
479
480 def tracked_dirs(&self) -> PyResult<PyList> {
480 def tracked_dirs(&self) -> PyResult<PyList> {
481 let dirs = PyList::new(py, &[]);
481 let dirs = PyList::new(py, &[]);
482 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
482 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
483 .map_err(|e |dirstate_error(py, e))?
483 .map_err(|e |dirstate_error(py, e))?
484 {
484 {
485 let path = path.map_err(|e| v2_error(py, e))?;
485 let path = path.map_err(|e| v2_error(py, e))?;
486 let path = PyBytes::new(py, path.as_bytes());
486 let path = PyBytes::new(py, path.as_bytes());
487 dirs.append(py, path.into_object())
487 dirs.append(py, path.into_object())
488 }
488 }
489 Ok(dirs)
489 Ok(dirs)
490 }
490 }
491
491
492 def setparents_fixup(&self) -> PyResult<PyDict> {
493 let dict = PyDict::new(py);
494 let copies = self.inner(py).borrow_mut().setparents_fixup();
495 for (key, value) in copies.map_err(|e| v2_error(py, e))? {
496 dict.set_item(
497 py,
498 PyBytes::new(py, key.as_bytes()),
499 PyBytes::new(py, value.as_bytes()),
500 )?;
501 }
502 Ok(dict)
503 }
504
492 def debug_iter(&self, all: bool) -> PyResult<PyList> {
505 def debug_iter(&self, all: bool) -> PyResult<PyList> {
493 let dirs = PyList::new(py, &[]);
506 let dirs = PyList::new(py, &[]);
494 for item in self.inner(py).borrow().debug_iter(all) {
507 for item in self.inner(py).borrow().debug_iter(all) {
495 let (path, (state, mode, size, mtime)) =
508 let (path, (state, mode, size, mtime)) =
496 item.map_err(|e| v2_error(py, e))?;
509 item.map_err(|e| v2_error(py, e))?;
497 let path = PyBytes::new(py, path.as_bytes());
510 let path = PyBytes::new(py, path.as_bytes());
498 let item = (path, state, mode, size, mtime);
511 let item = (path, state, mode, size, mtime);
499 dirs.append(py, item.to_py_object(py).into_object())
512 dirs.append(py, item.to_py_object(py).into_object())
500 }
513 }
501 Ok(dirs)
514 Ok(dirs)
502 }
515 }
503 });
516 });
504
517
505 impl DirstateMap {
518 impl DirstateMap {
506 pub fn get_inner_mut<'a>(
519 pub fn get_inner_mut<'a>(
507 &'a self,
520 &'a self,
508 py: Python<'a>,
521 py: Python<'a>,
509 ) -> RefMut<'a, OwningDirstateMap> {
522 ) -> RefMut<'a, OwningDirstateMap> {
510 self.inner(py).borrow_mut()
523 self.inner(py).borrow_mut()
511 }
524 }
512 fn translate_key(
525 fn translate_key(
513 py: Python,
526 py: Python,
514 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
527 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
515 ) -> PyResult<Option<PyBytes>> {
528 ) -> PyResult<Option<PyBytes>> {
516 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
529 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
517 Ok(Some(PyBytes::new(py, f.as_bytes())))
530 Ok(Some(PyBytes::new(py, f.as_bytes())))
518 }
531 }
519 fn translate_key_value(
532 fn translate_key_value(
520 py: Python,
533 py: Python,
521 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
534 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
522 ) -> PyResult<Option<(PyBytes, PyObject)>> {
535 ) -> PyResult<Option<(PyBytes, PyObject)>> {
523 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
536 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
524 Ok(Some((
537 Ok(Some((
525 PyBytes::new(py, f.as_bytes()),
538 PyBytes::new(py, f.as_bytes()),
526 DirstateItem::new_as_pyobject(py, entry)?,
539 DirstateItem::new_as_pyobject(py, entry)?,
527 )))
540 )))
528 }
541 }
529 }
542 }
530
543
531 py_shared_iterator!(
544 py_shared_iterator!(
532 DirstateMapKeysIterator,
545 DirstateMapKeysIterator,
533 UnsafePyLeaked<StateMapIter<'static>>,
546 UnsafePyLeaked<StateMapIter<'static>>,
534 DirstateMap::translate_key,
547 DirstateMap::translate_key,
535 Option<PyBytes>
548 Option<PyBytes>
536 );
549 );
537
550
538 py_shared_iterator!(
551 py_shared_iterator!(
539 DirstateMapItemsIterator,
552 DirstateMapItemsIterator,
540 UnsafePyLeaked<StateMapIter<'static>>,
553 UnsafePyLeaked<StateMapIter<'static>>,
541 DirstateMap::translate_key_value,
554 DirstateMap::translate_key_value,
542 Option<(PyBytes, PyObject)>
555 Option<(PyBytes, PyObject)>
543 );
556 );
544
557
545 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
558 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
546 let bytes = obj.extract::<PyBytes>(py)?;
559 let bytes = obj.extract::<PyBytes>(py)?;
547 match bytes.data(py).try_into() {
560 match bytes.data(py).try_into() {
548 Ok(s) => Ok(s),
561 Ok(s) => Ok(s),
549 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
562 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
550 }
563 }
551 }
564 }
552
565
553 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
566 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
554 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
567 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
555 }
568 }
556
569
557 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
570 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
558 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
571 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
559 }
572 }
General Comments 0
You need to be logged in to leave comments. Login now