##// END OF EJS Templates
dirstate: drop the `clearambiguoustimes` method for the map...
marmoute -
r48870:cd13d3c2 default
parent child Browse files
Show More
@@ -1,969 +1,959 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 from __future__ import absolute_import
6 from __future__ import absolute_import
7
7
8 import errno
8 import errno
9
9
10 from .i18n import _
10 from .i18n import _
11
11
12 from . import (
12 from . import (
13 error,
13 error,
14 pathutil,
14 pathutil,
15 policy,
15 policy,
16 pycompat,
16 pycompat,
17 txnutil,
17 txnutil,
18 util,
18 util,
19 )
19 )
20
20
21 from .dirstateutils import (
21 from .dirstateutils import (
22 docket as docketmod,
22 docket as docketmod,
23 )
23 )
24
24
25 parsers = policy.importmod('parsers')
25 parsers = policy.importmod('parsers')
26 rustmod = policy.importrust('dirstate')
26 rustmod = policy.importrust('dirstate')
27
27
28 propertycache = util.propertycache
28 propertycache = util.propertycache
29
29
30 if rustmod is None:
30 if rustmod is None:
31 DirstateItem = parsers.DirstateItem
31 DirstateItem = parsers.DirstateItem
32 else:
32 else:
33 DirstateItem = rustmod.DirstateItem
33 DirstateItem = rustmod.DirstateItem
34
34
35 rangemask = 0x7FFFFFFF
35 rangemask = 0x7FFFFFFF
36
36
37
37
38 class dirstatemap(object):
38 class dirstatemap(object):
39 """Map encapsulating the dirstate's contents.
39 """Map encapsulating the dirstate's contents.
40
40
41 The dirstate contains the following state:
41 The dirstate contains the following state:
42
42
43 - `identity` is the identity of the dirstate file, which can be used to
43 - `identity` is the identity of the dirstate file, which can be used to
44 detect when changes have occurred to the dirstate file.
44 detect when changes have occurred to the dirstate file.
45
45
46 - `parents` is a pair containing the parents of the working copy. The
46 - `parents` is a pair containing the parents of the working copy. The
47 parents are updated by calling `setparents`.
47 parents are updated by calling `setparents`.
48
48
49 - the state map maps filenames to tuples of (state, mode, size, mtime),
49 - the state map maps filenames to tuples of (state, mode, size, mtime),
50 where state is a single character representing 'normal', 'added',
50 where state is a single character representing 'normal', 'added',
51 'removed', or 'merged'. It is read by treating the dirstate as a
51 'removed', or 'merged'. It is read by treating the dirstate as a
52 dict. File state is updated by calling various methods (see each
52 dict. File state is updated by calling various methods (see each
53 documentation for details):
53 documentation for details):
54
54
55 - `reset_state`,
55 - `reset_state`,
56 - `set_tracked`
56 - `set_tracked`
57 - `set_untracked`
57 - `set_untracked`
58 - `set_clean`
58 - `set_clean`
59 - `set_possibly_dirty`
59 - `set_possibly_dirty`
60
60
61 - `copymap` maps destination filenames to their source filename.
61 - `copymap` maps destination filenames to their source filename.
62
62
63 The dirstate also provides the following views onto the state:
63 The dirstate also provides the following views onto the state:
64
64
65 - `nonnormalset` is a set of the filenames that have state other
65 - `nonnormalset` is a set of the filenames that have state other
66 than 'normal', or are normal but have an mtime of -1 ('normallookup').
66 than 'normal', or are normal but have an mtime of -1 ('normallookup').
67
67
68 - `otherparentset` is a set of the filenames that are marked as coming
68 - `otherparentset` is a set of the filenames that are marked as coming
69 from the second parent when the dirstate is currently being merged.
69 from the second parent when the dirstate is currently being merged.
70
70
71 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
71 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
72 form that they appear as in the dirstate.
72 form that they appear as in the dirstate.
73
73
74 - `dirfoldmap` is a dict mapping normalized directory names to the
74 - `dirfoldmap` is a dict mapping normalized directory names to the
75 denormalized form that they appear as in the dirstate.
75 denormalized form that they appear as in the dirstate.
76 """
76 """
77
77
78 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
78 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
79 self._ui = ui
79 self._ui = ui
80 self._opener = opener
80 self._opener = opener
81 self._root = root
81 self._root = root
82 self._filename = b'dirstate'
82 self._filename = b'dirstate'
83 self._nodelen = 20
83 self._nodelen = 20
84 self._nodeconstants = nodeconstants
84 self._nodeconstants = nodeconstants
85 assert (
85 assert (
86 not use_dirstate_v2
86 not use_dirstate_v2
87 ), "should have detected unsupported requirement"
87 ), "should have detected unsupported requirement"
88
88
89 self._parents = None
89 self._parents = None
90 self._dirtyparents = False
90 self._dirtyparents = False
91
91
92 # for consistent view between _pl() and _read() invocations
92 # for consistent view between _pl() and _read() invocations
93 self._pendingmode = None
93 self._pendingmode = None
94
94
95 @propertycache
95 @propertycache
96 def _map(self):
96 def _map(self):
97 self._map = {}
97 self._map = {}
98 self.read()
98 self.read()
99 return self._map
99 return self._map
100
100
101 @propertycache
101 @propertycache
102 def copymap(self):
102 def copymap(self):
103 self.copymap = {}
103 self.copymap = {}
104 self._map
104 self._map
105 return self.copymap
105 return self.copymap
106
106
107 def clear(self):
107 def clear(self):
108 self._map.clear()
108 self._map.clear()
109 self.copymap.clear()
109 self.copymap.clear()
110 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
110 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
111 util.clearcachedproperty(self, b"_dirs")
111 util.clearcachedproperty(self, b"_dirs")
112 util.clearcachedproperty(self, b"_alldirs")
112 util.clearcachedproperty(self, b"_alldirs")
113 util.clearcachedproperty(self, b"filefoldmap")
113 util.clearcachedproperty(self, b"filefoldmap")
114 util.clearcachedproperty(self, b"dirfoldmap")
114 util.clearcachedproperty(self, b"dirfoldmap")
115 util.clearcachedproperty(self, b"nonnormalset")
115 util.clearcachedproperty(self, b"nonnormalset")
116 util.clearcachedproperty(self, b"otherparentset")
116 util.clearcachedproperty(self, b"otherparentset")
117
117
118 def items(self):
118 def items(self):
119 return pycompat.iteritems(self._map)
119 return pycompat.iteritems(self._map)
120
120
121 # forward for python2,3 compat
121 # forward for python2,3 compat
122 iteritems = items
122 iteritems = items
123
123
124 def debug_iter(self, all):
124 def debug_iter(self, all):
125 """
125 """
126 Return an iterator of (filename, state, mode, size, mtime) tuples
126 Return an iterator of (filename, state, mode, size, mtime) tuples
127
127
128 `all` is unused when Rust is not enabled
128 `all` is unused when Rust is not enabled
129 """
129 """
130 for (filename, item) in self.items():
130 for (filename, item) in self.items():
131 yield (filename, item.state, item.mode, item.size, item.mtime)
131 yield (filename, item.state, item.mode, item.size, item.mtime)
132
132
133 def __len__(self):
133 def __len__(self):
134 return len(self._map)
134 return len(self._map)
135
135
136 def __iter__(self):
136 def __iter__(self):
137 return iter(self._map)
137 return iter(self._map)
138
138
139 def get(self, key, default=None):
139 def get(self, key, default=None):
140 return self._map.get(key, default)
140 return self._map.get(key, default)
141
141
142 def __contains__(self, key):
142 def __contains__(self, key):
143 return key in self._map
143 return key in self._map
144
144
145 def __getitem__(self, key):
145 def __getitem__(self, key):
146 return self._map[key]
146 return self._map[key]
147
147
148 def keys(self):
148 def keys(self):
149 return self._map.keys()
149 return self._map.keys()
150
150
151 def preload(self):
151 def preload(self):
152 """Loads the underlying data, if it's not already loaded"""
152 """Loads the underlying data, if it's not already loaded"""
153 self._map
153 self._map
154
154
155 def _dirs_incr(self, filename, old_entry=None):
155 def _dirs_incr(self, filename, old_entry=None):
156 """incremente the dirstate counter if applicable"""
156 """incremente the dirstate counter if applicable"""
157 if (
157 if (
158 old_entry is None or old_entry.removed
158 old_entry is None or old_entry.removed
159 ) and "_dirs" in self.__dict__:
159 ) and "_dirs" in self.__dict__:
160 self._dirs.addpath(filename)
160 self._dirs.addpath(filename)
161 if old_entry is None and "_alldirs" in self.__dict__:
161 if old_entry is None and "_alldirs" in self.__dict__:
162 self._alldirs.addpath(filename)
162 self._alldirs.addpath(filename)
163
163
164 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
164 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
165 """decremente the dirstate counter if applicable"""
165 """decremente the dirstate counter if applicable"""
166 if old_entry is not None:
166 if old_entry is not None:
167 if "_dirs" in self.__dict__ and not old_entry.removed:
167 if "_dirs" in self.__dict__ and not old_entry.removed:
168 self._dirs.delpath(filename)
168 self._dirs.delpath(filename)
169 if "_alldirs" in self.__dict__ and not remove_variant:
169 if "_alldirs" in self.__dict__ and not remove_variant:
170 self._alldirs.delpath(filename)
170 self._alldirs.delpath(filename)
171 elif remove_variant and "_alldirs" in self.__dict__:
171 elif remove_variant and "_alldirs" in self.__dict__:
172 self._alldirs.addpath(filename)
172 self._alldirs.addpath(filename)
173 if "filefoldmap" in self.__dict__:
173 if "filefoldmap" in self.__dict__:
174 normed = util.normcase(filename)
174 normed = util.normcase(filename)
175 self.filefoldmap.pop(normed, None)
175 self.filefoldmap.pop(normed, None)
176
176
177 def set_possibly_dirty(self, filename):
177 def set_possibly_dirty(self, filename):
178 """record that the current state of the file on disk is unknown"""
178 """record that the current state of the file on disk is unknown"""
179 self[filename].set_possibly_dirty()
179 self[filename].set_possibly_dirty()
180
180
181 def set_clean(self, filename, mode, size, mtime):
181 def set_clean(self, filename, mode, size, mtime):
182 """mark a file as back to a clean state"""
182 """mark a file as back to a clean state"""
183 entry = self[filename]
183 entry = self[filename]
184 mtime = mtime & rangemask
184 mtime = mtime & rangemask
185 size = size & rangemask
185 size = size & rangemask
186 entry.set_clean(mode, size, mtime)
186 entry.set_clean(mode, size, mtime)
187 self.copymap.pop(filename, None)
187 self.copymap.pop(filename, None)
188 self.nonnormalset.discard(filename)
188 self.nonnormalset.discard(filename)
189
189
190 def reset_state(
190 def reset_state(
191 self,
191 self,
192 filename,
192 filename,
193 wc_tracked=False,
193 wc_tracked=False,
194 p1_tracked=False,
194 p1_tracked=False,
195 p2_tracked=False,
195 p2_tracked=False,
196 merged=False,
196 merged=False,
197 clean_p1=False,
197 clean_p1=False,
198 clean_p2=False,
198 clean_p2=False,
199 possibly_dirty=False,
199 possibly_dirty=False,
200 parentfiledata=None,
200 parentfiledata=None,
201 ):
201 ):
202 """Set a entry to a given state, diregarding all previous state
202 """Set a entry to a given state, diregarding all previous state
203
203
204 This is to be used by the part of the dirstate API dedicated to
204 This is to be used by the part of the dirstate API dedicated to
205 adjusting the dirstate after a update/merge.
205 adjusting the dirstate after a update/merge.
206
206
207 note: calling this might result to no entry existing at all if the
207 note: calling this might result to no entry existing at all if the
208 dirstate map does not see any point at having one for this file
208 dirstate map does not see any point at having one for this file
209 anymore.
209 anymore.
210 """
210 """
211 if merged and (clean_p1 or clean_p2):
211 if merged and (clean_p1 or clean_p2):
212 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
212 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
213 raise error.ProgrammingError(msg)
213 raise error.ProgrammingError(msg)
214 # copy information are now outdated
214 # copy information are now outdated
215 # (maybe new information should be in directly passed to this function)
215 # (maybe new information should be in directly passed to this function)
216 self.copymap.pop(filename, None)
216 self.copymap.pop(filename, None)
217
217
218 if not (p1_tracked or p2_tracked or wc_tracked):
218 if not (p1_tracked or p2_tracked or wc_tracked):
219 old_entry = self._map.pop(filename, None)
219 old_entry = self._map.pop(filename, None)
220 self._dirs_decr(filename, old_entry=old_entry)
220 self._dirs_decr(filename, old_entry=old_entry)
221 self.nonnormalset.discard(filename)
221 self.nonnormalset.discard(filename)
222 self.copymap.pop(filename, None)
222 self.copymap.pop(filename, None)
223 return
223 return
224 elif merged:
224 elif merged:
225 # XXX might be merged and removed ?
225 # XXX might be merged and removed ?
226 entry = self.get(filename)
226 entry = self.get(filename)
227 if entry is None or not entry.tracked:
227 if entry is None or not entry.tracked:
228 # XXX mostly replicate dirstate.other parent. We should get
228 # XXX mostly replicate dirstate.other parent. We should get
229 # the higher layer to pass us more reliable data where `merged`
229 # the higher layer to pass us more reliable data where `merged`
230 # actually mean merged. Dropping this clause will show failure
230 # actually mean merged. Dropping this clause will show failure
231 # in `test-graft.t`
231 # in `test-graft.t`
232 merged = False
232 merged = False
233 clean_p2 = True
233 clean_p2 = True
234 elif not (p1_tracked or p2_tracked) and wc_tracked:
234 elif not (p1_tracked or p2_tracked) and wc_tracked:
235 pass # file is added, nothing special to adjust
235 pass # file is added, nothing special to adjust
236 elif (p1_tracked or p2_tracked) and not wc_tracked:
236 elif (p1_tracked or p2_tracked) and not wc_tracked:
237 pass
237 pass
238 elif clean_p2 and wc_tracked:
238 elif clean_p2 and wc_tracked:
239 if p1_tracked or self.get(filename) is not None:
239 if p1_tracked or self.get(filename) is not None:
240 # XXX the `self.get` call is catching some case in
240 # XXX the `self.get` call is catching some case in
241 # `test-merge-remove.t` where the file is tracked in p1, the
241 # `test-merge-remove.t` where the file is tracked in p1, the
242 # p1_tracked argument is False.
242 # p1_tracked argument is False.
243 #
243 #
244 # In addition, this seems to be a case where the file is marked
244 # In addition, this seems to be a case where the file is marked
245 # as merged without actually being the result of a merge
245 # as merged without actually being the result of a merge
246 # action. So thing are not ideal here.
246 # action. So thing are not ideal here.
247 merged = True
247 merged = True
248 clean_p2 = False
248 clean_p2 = False
249 elif not p1_tracked and p2_tracked and wc_tracked:
249 elif not p1_tracked and p2_tracked and wc_tracked:
250 clean_p2 = True
250 clean_p2 = True
251 elif possibly_dirty:
251 elif possibly_dirty:
252 pass
252 pass
253 elif wc_tracked:
253 elif wc_tracked:
254 # this is a "normal" file
254 # this is a "normal" file
255 if parentfiledata is None:
255 if parentfiledata is None:
256 msg = b'failed to pass parentfiledata for a normal file: %s'
256 msg = b'failed to pass parentfiledata for a normal file: %s'
257 msg %= filename
257 msg %= filename
258 raise error.ProgrammingError(msg)
258 raise error.ProgrammingError(msg)
259 else:
259 else:
260 assert False, 'unreachable'
260 assert False, 'unreachable'
261
261
262 old_entry = self._map.get(filename)
262 old_entry = self._map.get(filename)
263 self._dirs_incr(filename, old_entry)
263 self._dirs_incr(filename, old_entry)
264 entry = DirstateItem(
264 entry = DirstateItem(
265 wc_tracked=wc_tracked,
265 wc_tracked=wc_tracked,
266 p1_tracked=p1_tracked,
266 p1_tracked=p1_tracked,
267 p2_tracked=p2_tracked,
267 p2_tracked=p2_tracked,
268 merged=merged,
268 merged=merged,
269 clean_p1=clean_p1,
269 clean_p1=clean_p1,
270 clean_p2=clean_p2,
270 clean_p2=clean_p2,
271 possibly_dirty=possibly_dirty,
271 possibly_dirty=possibly_dirty,
272 parentfiledata=parentfiledata,
272 parentfiledata=parentfiledata,
273 )
273 )
274 if entry.dm_nonnormal:
274 if entry.dm_nonnormal:
275 self.nonnormalset.add(filename)
275 self.nonnormalset.add(filename)
276 else:
276 else:
277 self.nonnormalset.discard(filename)
277 self.nonnormalset.discard(filename)
278 if entry.dm_otherparent:
278 if entry.dm_otherparent:
279 self.otherparentset.add(filename)
279 self.otherparentset.add(filename)
280 else:
280 else:
281 self.otherparentset.discard(filename)
281 self.otherparentset.discard(filename)
282 self._map[filename] = entry
282 self._map[filename] = entry
283
283
284 def set_tracked(self, filename):
284 def set_tracked(self, filename):
285 new = False
285 new = False
286 entry = self.get(filename)
286 entry = self.get(filename)
287 if entry is None:
287 if entry is None:
288 self._dirs_incr(filename)
288 self._dirs_incr(filename)
289 entry = DirstateItem(
289 entry = DirstateItem(
290 p1_tracked=False,
290 p1_tracked=False,
291 p2_tracked=False,
291 p2_tracked=False,
292 wc_tracked=True,
292 wc_tracked=True,
293 merged=False,
293 merged=False,
294 clean_p1=False,
294 clean_p1=False,
295 clean_p2=False,
295 clean_p2=False,
296 possibly_dirty=False,
296 possibly_dirty=False,
297 parentfiledata=None,
297 parentfiledata=None,
298 )
298 )
299 self._map[filename] = entry
299 self._map[filename] = entry
300 if entry.dm_nonnormal:
300 if entry.dm_nonnormal:
301 self.nonnormalset.add(filename)
301 self.nonnormalset.add(filename)
302 new = True
302 new = True
303 elif not entry.tracked:
303 elif not entry.tracked:
304 self._dirs_incr(filename, entry)
304 self._dirs_incr(filename, entry)
305 entry.set_tracked()
305 entry.set_tracked()
306 new = True
306 new = True
307 else:
307 else:
308 # XXX This is probably overkill for more case, but we need this to
308 # XXX This is probably overkill for more case, but we need this to
309 # fully replace the `normallookup` call with `set_tracked` one.
309 # fully replace the `normallookup` call with `set_tracked` one.
310 # Consider smoothing this in the future.
310 # Consider smoothing this in the future.
311 self.set_possibly_dirty(filename)
311 self.set_possibly_dirty(filename)
312 return new
312 return new
313
313
314 def set_untracked(self, f):
314 def set_untracked(self, f):
315 """Mark a file as no longer tracked in the dirstate map"""
315 """Mark a file as no longer tracked in the dirstate map"""
316 entry = self.get(f)
316 entry = self.get(f)
317 if entry is None:
317 if entry is None:
318 return False
318 return False
319 else:
319 else:
320 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
320 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
321 if not entry.merged:
321 if not entry.merged:
322 self.copymap.pop(f, None)
322 self.copymap.pop(f, None)
323 if entry.added:
323 if entry.added:
324 self.nonnormalset.discard(f)
324 self.nonnormalset.discard(f)
325 self._map.pop(f, None)
325 self._map.pop(f, None)
326 else:
326 else:
327 self.nonnormalset.add(f)
327 self.nonnormalset.add(f)
328 if entry.from_p2:
328 if entry.from_p2:
329 self.otherparentset.add(f)
329 self.otherparentset.add(f)
330 entry.set_untracked()
330 entry.set_untracked()
331 return True
331 return True
332
332
333 def clearambiguoustimes(self, files, now):
334 for f in files:
335 e = self.get(f)
336 if e is not None and e.need_delay(now):
337 e.set_possibly_dirty()
338 self.nonnormalset.add(f)
339
340 def nonnormalentries(self):
333 def nonnormalentries(self):
341 '''Compute the nonnormal dirstate entries from the dmap'''
334 '''Compute the nonnormal dirstate entries from the dmap'''
342 try:
335 try:
343 return parsers.nonnormalotherparententries(self._map)
336 return parsers.nonnormalotherparententries(self._map)
344 except AttributeError:
337 except AttributeError:
345 nonnorm = set()
338 nonnorm = set()
346 otherparent = set()
339 otherparent = set()
347 for fname, e in pycompat.iteritems(self._map):
340 for fname, e in pycompat.iteritems(self._map):
348 if e.dm_nonnormal:
341 if e.dm_nonnormal:
349 nonnorm.add(fname)
342 nonnorm.add(fname)
350 if e.from_p2:
343 if e.from_p2:
351 otherparent.add(fname)
344 otherparent.add(fname)
352 return nonnorm, otherparent
345 return nonnorm, otherparent
353
346
354 @propertycache
347 @propertycache
355 def filefoldmap(self):
348 def filefoldmap(self):
356 """Returns a dictionary mapping normalized case paths to their
349 """Returns a dictionary mapping normalized case paths to their
357 non-normalized versions.
350 non-normalized versions.
358 """
351 """
359 try:
352 try:
360 makefilefoldmap = parsers.make_file_foldmap
353 makefilefoldmap = parsers.make_file_foldmap
361 except AttributeError:
354 except AttributeError:
362 pass
355 pass
363 else:
356 else:
364 return makefilefoldmap(
357 return makefilefoldmap(
365 self._map, util.normcasespec, util.normcasefallback
358 self._map, util.normcasespec, util.normcasefallback
366 )
359 )
367
360
368 f = {}
361 f = {}
369 normcase = util.normcase
362 normcase = util.normcase
370 for name, s in pycompat.iteritems(self._map):
363 for name, s in pycompat.iteritems(self._map):
371 if not s.removed:
364 if not s.removed:
372 f[normcase(name)] = name
365 f[normcase(name)] = name
373 f[b'.'] = b'.' # prevents useless util.fspath() invocation
366 f[b'.'] = b'.' # prevents useless util.fspath() invocation
374 return f
367 return f
375
368
376 def hastrackeddir(self, d):
369 def hastrackeddir(self, d):
377 """
370 """
378 Returns True if the dirstate contains a tracked (not removed) file
371 Returns True if the dirstate contains a tracked (not removed) file
379 in this directory.
372 in this directory.
380 """
373 """
381 return d in self._dirs
374 return d in self._dirs
382
375
383 def hasdir(self, d):
376 def hasdir(self, d):
384 """
377 """
385 Returns True if the dirstate contains a file (tracked or removed)
378 Returns True if the dirstate contains a file (tracked or removed)
386 in this directory.
379 in this directory.
387 """
380 """
388 return d in self._alldirs
381 return d in self._alldirs
389
382
390 @propertycache
383 @propertycache
391 def _dirs(self):
384 def _dirs(self):
392 return pathutil.dirs(self._map, only_tracked=True)
385 return pathutil.dirs(self._map, only_tracked=True)
393
386
394 @propertycache
387 @propertycache
395 def _alldirs(self):
388 def _alldirs(self):
396 return pathutil.dirs(self._map)
389 return pathutil.dirs(self._map)
397
390
398 def _opendirstatefile(self):
391 def _opendirstatefile(self):
399 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
392 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
400 if self._pendingmode is not None and self._pendingmode != mode:
393 if self._pendingmode is not None and self._pendingmode != mode:
401 fp.close()
394 fp.close()
402 raise error.Abort(
395 raise error.Abort(
403 _(b'working directory state may be changed parallelly')
396 _(b'working directory state may be changed parallelly')
404 )
397 )
405 self._pendingmode = mode
398 self._pendingmode = mode
406 return fp
399 return fp
407
400
408 def parents(self):
401 def parents(self):
409 if not self._parents:
402 if not self._parents:
410 try:
403 try:
411 fp = self._opendirstatefile()
404 fp = self._opendirstatefile()
412 st = fp.read(2 * self._nodelen)
405 st = fp.read(2 * self._nodelen)
413 fp.close()
406 fp.close()
414 except IOError as err:
407 except IOError as err:
415 if err.errno != errno.ENOENT:
408 if err.errno != errno.ENOENT:
416 raise
409 raise
417 # File doesn't exist, so the current state is empty
410 # File doesn't exist, so the current state is empty
418 st = b''
411 st = b''
419
412
420 l = len(st)
413 l = len(st)
421 if l == self._nodelen * 2:
414 if l == self._nodelen * 2:
422 self._parents = (
415 self._parents = (
423 st[: self._nodelen],
416 st[: self._nodelen],
424 st[self._nodelen : 2 * self._nodelen],
417 st[self._nodelen : 2 * self._nodelen],
425 )
418 )
426 elif l == 0:
419 elif l == 0:
427 self._parents = (
420 self._parents = (
428 self._nodeconstants.nullid,
421 self._nodeconstants.nullid,
429 self._nodeconstants.nullid,
422 self._nodeconstants.nullid,
430 )
423 )
431 else:
424 else:
432 raise error.Abort(
425 raise error.Abort(
433 _(b'working directory state appears damaged!')
426 _(b'working directory state appears damaged!')
434 )
427 )
435
428
436 return self._parents
429 return self._parents
437
430
438 def setparents(self, p1, p2):
431 def setparents(self, p1, p2):
439 self._parents = (p1, p2)
432 self._parents = (p1, p2)
440 self._dirtyparents = True
433 self._dirtyparents = True
441
434
442 def read(self):
435 def read(self):
443 # ignore HG_PENDING because identity is used only for writing
436 # ignore HG_PENDING because identity is used only for writing
444 self.identity = util.filestat.frompath(
437 self.identity = util.filestat.frompath(
445 self._opener.join(self._filename)
438 self._opener.join(self._filename)
446 )
439 )
447
440
448 try:
441 try:
449 fp = self._opendirstatefile()
442 fp = self._opendirstatefile()
450 try:
443 try:
451 st = fp.read()
444 st = fp.read()
452 finally:
445 finally:
453 fp.close()
446 fp.close()
454 except IOError as err:
447 except IOError as err:
455 if err.errno != errno.ENOENT:
448 if err.errno != errno.ENOENT:
456 raise
449 raise
457 return
450 return
458 if not st:
451 if not st:
459 return
452 return
460
453
461 if util.safehasattr(parsers, b'dict_new_presized'):
454 if util.safehasattr(parsers, b'dict_new_presized'):
462 # Make an estimate of the number of files in the dirstate based on
455 # Make an estimate of the number of files in the dirstate based on
463 # its size. This trades wasting some memory for avoiding costly
456 # its size. This trades wasting some memory for avoiding costly
464 # resizes. Each entry have a prefix of 17 bytes followed by one or
457 # resizes. Each entry have a prefix of 17 bytes followed by one or
465 # two path names. Studies on various large-scale real-world repositories
458 # two path names. Studies on various large-scale real-world repositories
466 # found 54 bytes a reasonable upper limit for the average path names.
459 # found 54 bytes a reasonable upper limit for the average path names.
467 # Copy entries are ignored for the sake of this estimate.
460 # Copy entries are ignored for the sake of this estimate.
468 self._map = parsers.dict_new_presized(len(st) // 71)
461 self._map = parsers.dict_new_presized(len(st) // 71)
469
462
470 # Python's garbage collector triggers a GC each time a certain number
463 # Python's garbage collector triggers a GC each time a certain number
471 # of container objects (the number being defined by
464 # of container objects (the number being defined by
472 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
465 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
473 # for each file in the dirstate. The C version then immediately marks
466 # for each file in the dirstate. The C version then immediately marks
474 # them as not to be tracked by the collector. However, this has no
467 # them as not to be tracked by the collector. However, this has no
475 # effect on when GCs are triggered, only on what objects the GC looks
468 # effect on when GCs are triggered, only on what objects the GC looks
476 # into. This means that O(number of files) GCs are unavoidable.
469 # into. This means that O(number of files) GCs are unavoidable.
477 # Depending on when in the process's lifetime the dirstate is parsed,
470 # Depending on when in the process's lifetime the dirstate is parsed,
478 # this can get very expensive. As a workaround, disable GC while
471 # this can get very expensive. As a workaround, disable GC while
479 # parsing the dirstate.
472 # parsing the dirstate.
480 #
473 #
481 # (we cannot decorate the function directly since it is in a C module)
474 # (we cannot decorate the function directly since it is in a C module)
482 parse_dirstate = util.nogc(parsers.parse_dirstate)
475 parse_dirstate = util.nogc(parsers.parse_dirstate)
483 p = parse_dirstate(self._map, self.copymap, st)
476 p = parse_dirstate(self._map, self.copymap, st)
484 if not self._dirtyparents:
477 if not self._dirtyparents:
485 self.setparents(*p)
478 self.setparents(*p)
486
479
487 # Avoid excess attribute lookups by fast pathing certain checks
480 # Avoid excess attribute lookups by fast pathing certain checks
488 self.__contains__ = self._map.__contains__
481 self.__contains__ = self._map.__contains__
489 self.__getitem__ = self._map.__getitem__
482 self.__getitem__ = self._map.__getitem__
490 self.get = self._map.get
483 self.get = self._map.get
491
484
492 def write(self, _tr, st, now):
485 def write(self, _tr, st, now):
493 st.write(
486 st.write(
494 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
487 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
495 )
488 )
496 st.close()
489 st.close()
497 self._dirtyparents = False
490 self._dirtyparents = False
498 self.nonnormalset, self.otherparentset = self.nonnormalentries()
491 self.nonnormalset, self.otherparentset = self.nonnormalentries()
499
492
500 @propertycache
493 @propertycache
501 def nonnormalset(self):
494 def nonnormalset(self):
502 nonnorm, otherparents = self.nonnormalentries()
495 nonnorm, otherparents = self.nonnormalentries()
503 self.otherparentset = otherparents
496 self.otherparentset = otherparents
504 return nonnorm
497 return nonnorm
505
498
506 @propertycache
499 @propertycache
507 def otherparentset(self):
500 def otherparentset(self):
508 nonnorm, otherparents = self.nonnormalentries()
501 nonnorm, otherparents = self.nonnormalentries()
509 self.nonnormalset = nonnorm
502 self.nonnormalset = nonnorm
510 return otherparents
503 return otherparents
511
504
512 def non_normal_or_other_parent_paths(self):
505 def non_normal_or_other_parent_paths(self):
513 return self.nonnormalset.union(self.otherparentset)
506 return self.nonnormalset.union(self.otherparentset)
514
507
515 @propertycache
508 @propertycache
516 def identity(self):
509 def identity(self):
517 self._map
510 self._map
518 return self.identity
511 return self.identity
519
512
520 @propertycache
513 @propertycache
521 def dirfoldmap(self):
514 def dirfoldmap(self):
522 f = {}
515 f = {}
523 normcase = util.normcase
516 normcase = util.normcase
524 for name in self._dirs:
517 for name in self._dirs:
525 f[normcase(name)] = name
518 f[normcase(name)] = name
526 return f
519 return f
527
520
528
521
529 if rustmod is not None:
522 if rustmod is not None:
530
523
531 class dirstatemap(object):
524 class dirstatemap(object):
532 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
525 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
533 self._use_dirstate_v2 = use_dirstate_v2
526 self._use_dirstate_v2 = use_dirstate_v2
534 self._nodeconstants = nodeconstants
527 self._nodeconstants = nodeconstants
535 self._ui = ui
528 self._ui = ui
536 self._opener = opener
529 self._opener = opener
537 self._root = root
530 self._root = root
538 self._filename = b'dirstate'
531 self._filename = b'dirstate'
539 self._nodelen = 20 # Also update Rust code when changing this!
532 self._nodelen = 20 # Also update Rust code when changing this!
540 self._parents = None
533 self._parents = None
541 self._dirtyparents = False
534 self._dirtyparents = False
542 self._docket = None
535 self._docket = None
543
536
544 # for consistent view between _pl() and _read() invocations
537 # for consistent view between _pl() and _read() invocations
545 self._pendingmode = None
538 self._pendingmode = None
546
539
547 self._use_dirstate_tree = self._ui.configbool(
540 self._use_dirstate_tree = self._ui.configbool(
548 b"experimental",
541 b"experimental",
549 b"dirstate-tree.in-memory",
542 b"dirstate-tree.in-memory",
550 False,
543 False,
551 )
544 )
552
545
553 def addfile(
546 def addfile(
554 self,
547 self,
555 f,
548 f,
556 mode=0,
549 mode=0,
557 size=None,
550 size=None,
558 mtime=None,
551 mtime=None,
559 added=False,
552 added=False,
560 merged=False,
553 merged=False,
561 from_p2=False,
554 from_p2=False,
562 possibly_dirty=False,
555 possibly_dirty=False,
563 ):
556 ):
564 if added:
557 if added:
565 assert not possibly_dirty
558 assert not possibly_dirty
566 assert not from_p2
559 assert not from_p2
567 item = DirstateItem.new_added()
560 item = DirstateItem.new_added()
568 elif merged:
561 elif merged:
569 assert not possibly_dirty
562 assert not possibly_dirty
570 assert not from_p2
563 assert not from_p2
571 item = DirstateItem.new_merged()
564 item = DirstateItem.new_merged()
572 elif from_p2:
565 elif from_p2:
573 assert not possibly_dirty
566 assert not possibly_dirty
574 item = DirstateItem.new_from_p2()
567 item = DirstateItem.new_from_p2()
575 elif possibly_dirty:
568 elif possibly_dirty:
576 item = DirstateItem.new_possibly_dirty()
569 item = DirstateItem.new_possibly_dirty()
577 else:
570 else:
578 assert size is not None
571 assert size is not None
579 assert mtime is not None
572 assert mtime is not None
580 size = size & rangemask
573 size = size & rangemask
581 mtime = mtime & rangemask
574 mtime = mtime & rangemask
582 item = DirstateItem.new_normal(mode, size, mtime)
575 item = DirstateItem.new_normal(mode, size, mtime)
583 self._rustmap.addfile(f, item)
576 self._rustmap.addfile(f, item)
584 if added:
577 if added:
585 self.copymap.pop(f, None)
578 self.copymap.pop(f, None)
586
579
587 def reset_state(
580 def reset_state(
588 self,
581 self,
589 filename,
582 filename,
590 wc_tracked=False,
583 wc_tracked=False,
591 p1_tracked=False,
584 p1_tracked=False,
592 p2_tracked=False,
585 p2_tracked=False,
593 merged=False,
586 merged=False,
594 clean_p1=False,
587 clean_p1=False,
595 clean_p2=False,
588 clean_p2=False,
596 possibly_dirty=False,
589 possibly_dirty=False,
597 parentfiledata=None,
590 parentfiledata=None,
598 ):
591 ):
599 """Set a entry to a given state, disregarding all previous state
592 """Set a entry to a given state, disregarding all previous state
600
593
601 This is to be used by the part of the dirstate API dedicated to
594 This is to be used by the part of the dirstate API dedicated to
602 adjusting the dirstate after a update/merge.
595 adjusting the dirstate after a update/merge.
603
596
604 note: calling this might result to no entry existing at all if the
597 note: calling this might result to no entry existing at all if the
605 dirstate map does not see any point at having one for this file
598 dirstate map does not see any point at having one for this file
606 anymore.
599 anymore.
607 """
600 """
608 if merged and (clean_p1 or clean_p2):
601 if merged and (clean_p1 or clean_p2):
609 msg = (
602 msg = (
610 b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
603 b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
611 )
604 )
612 raise error.ProgrammingError(msg)
605 raise error.ProgrammingError(msg)
613 # copy information are now outdated
606 # copy information are now outdated
614 # (maybe new information should be in directly passed to this function)
607 # (maybe new information should be in directly passed to this function)
615 self.copymap.pop(filename, None)
608 self.copymap.pop(filename, None)
616
609
617 if not (p1_tracked or p2_tracked or wc_tracked):
610 if not (p1_tracked or p2_tracked or wc_tracked):
618 self._rustmap.drop_item_and_copy_source(filename)
611 self._rustmap.drop_item_and_copy_source(filename)
619 elif merged:
612 elif merged:
620 # XXX might be merged and removed ?
613 # XXX might be merged and removed ?
621 entry = self.get(filename)
614 entry = self.get(filename)
622 if entry is not None and entry.tracked:
615 if entry is not None and entry.tracked:
623 # XXX mostly replicate dirstate.other parent. We should get
616 # XXX mostly replicate dirstate.other parent. We should get
624 # the higher layer to pass us more reliable data where `merged`
617 # the higher layer to pass us more reliable data where `merged`
625 # actually mean merged. Dropping the else clause will show
618 # actually mean merged. Dropping the else clause will show
626 # failure in `test-graft.t`
619 # failure in `test-graft.t`
627 self.addfile(filename, merged=True)
620 self.addfile(filename, merged=True)
628 else:
621 else:
629 self.addfile(filename, from_p2=True)
622 self.addfile(filename, from_p2=True)
630 elif not (p1_tracked or p2_tracked) and wc_tracked:
623 elif not (p1_tracked or p2_tracked) and wc_tracked:
631 self.addfile(
624 self.addfile(
632 filename, added=True, possibly_dirty=possibly_dirty
625 filename, added=True, possibly_dirty=possibly_dirty
633 )
626 )
634 elif (p1_tracked or p2_tracked) and not wc_tracked:
627 elif (p1_tracked or p2_tracked) and not wc_tracked:
635 # XXX might be merged and removed ?
628 # XXX might be merged and removed ?
636 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
629 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
637 self.nonnormalset.add(filename)
630 self.nonnormalset.add(filename)
638 elif clean_p2 and wc_tracked:
631 elif clean_p2 and wc_tracked:
639 if p1_tracked or self.get(filename) is not None:
632 if p1_tracked or self.get(filename) is not None:
640 # XXX the `self.get` call is catching some case in
633 # XXX the `self.get` call is catching some case in
641 # `test-merge-remove.t` where the file is tracked in p1, the
634 # `test-merge-remove.t` where the file is tracked in p1, the
642 # p1_tracked argument is False.
635 # p1_tracked argument is False.
643 #
636 #
644 # In addition, this seems to be a case where the file is marked
637 # In addition, this seems to be a case where the file is marked
645 # as merged without actually being the result of a merge
638 # as merged without actually being the result of a merge
646 # action. So thing are not ideal here.
639 # action. So thing are not ideal here.
647 self.addfile(filename, merged=True)
640 self.addfile(filename, merged=True)
648 else:
641 else:
649 self.addfile(filename, from_p2=True)
642 self.addfile(filename, from_p2=True)
650 elif not p1_tracked and p2_tracked and wc_tracked:
643 elif not p1_tracked and p2_tracked and wc_tracked:
651 self.addfile(
644 self.addfile(
652 filename, from_p2=True, possibly_dirty=possibly_dirty
645 filename, from_p2=True, possibly_dirty=possibly_dirty
653 )
646 )
654 elif possibly_dirty:
647 elif possibly_dirty:
655 self.addfile(filename, possibly_dirty=possibly_dirty)
648 self.addfile(filename, possibly_dirty=possibly_dirty)
656 elif wc_tracked:
649 elif wc_tracked:
657 # this is a "normal" file
650 # this is a "normal" file
658 if parentfiledata is None:
651 if parentfiledata is None:
659 msg = b'failed to pass parentfiledata for a normal file: %s'
652 msg = b'failed to pass parentfiledata for a normal file: %s'
660 msg %= filename
653 msg %= filename
661 raise error.ProgrammingError(msg)
654 raise error.ProgrammingError(msg)
662 mode, size, mtime = parentfiledata
655 mode, size, mtime = parentfiledata
663 self.addfile(filename, mode=mode, size=size, mtime=mtime)
656 self.addfile(filename, mode=mode, size=size, mtime=mtime)
664 self.nonnormalset.discard(filename)
657 self.nonnormalset.discard(filename)
665 else:
658 else:
666 assert False, 'unreachable'
659 assert False, 'unreachable'
667
660
668 def set_tracked(self, filename):
661 def set_tracked(self, filename):
669 new = False
662 new = False
670 entry = self.get(filename)
663 entry = self.get(filename)
671 if entry is None:
664 if entry is None:
672 self.addfile(filename, added=True)
665 self.addfile(filename, added=True)
673 new = True
666 new = True
674 elif not entry.tracked:
667 elif not entry.tracked:
675 entry.set_tracked()
668 entry.set_tracked()
676 self._rustmap.set_dirstate_item(filename, entry)
669 self._rustmap.set_dirstate_item(filename, entry)
677 new = True
670 new = True
678 else:
671 else:
679 # XXX This is probably overkill for more case, but we need this to
672 # XXX This is probably overkill for more case, but we need this to
680 # fully replace the `normallookup` call with `set_tracked` one.
673 # fully replace the `normallookup` call with `set_tracked` one.
681 # Consider smoothing this in the future.
674 # Consider smoothing this in the future.
682 self.set_possibly_dirty(filename)
675 self.set_possibly_dirty(filename)
683 return new
676 return new
684
677
685 def set_untracked(self, f):
678 def set_untracked(self, f):
686 """Mark a file as no longer tracked in the dirstate map"""
679 """Mark a file as no longer tracked in the dirstate map"""
687 # in merge is only trigger more logic, so it "fine" to pass it.
680 # in merge is only trigger more logic, so it "fine" to pass it.
688 #
681 #
689 # the inner rust dirstate map code need to be adjusted once the API
682 # the inner rust dirstate map code need to be adjusted once the API
690 # for dirstate/dirstatemap/DirstateItem is a bit more settled
683 # for dirstate/dirstatemap/DirstateItem is a bit more settled
691 entry = self.get(f)
684 entry = self.get(f)
692 if entry is None:
685 if entry is None:
693 return False
686 return False
694 else:
687 else:
695 if entry.added:
688 if entry.added:
696 self._rustmap.drop_item_and_copy_source(f)
689 self._rustmap.drop_item_and_copy_source(f)
697 else:
690 else:
698 self._rustmap.removefile(f, in_merge=True)
691 self._rustmap.removefile(f, in_merge=True)
699 return True
692 return True
700
693
701 def removefile(self, *args, **kwargs):
694 def removefile(self, *args, **kwargs):
702 return self._rustmap.removefile(*args, **kwargs)
695 return self._rustmap.removefile(*args, **kwargs)
703
696
704 def clearambiguoustimes(self, *args, **kwargs):
705 return self._rustmap.clearambiguoustimes(*args, **kwargs)
706
707 def nonnormalentries(self):
697 def nonnormalentries(self):
708 return self._rustmap.nonnormalentries()
698 return self._rustmap.nonnormalentries()
709
699
710 def get(self, *args, **kwargs):
700 def get(self, *args, **kwargs):
711 return self._rustmap.get(*args, **kwargs)
701 return self._rustmap.get(*args, **kwargs)
712
702
713 @property
703 @property
714 def copymap(self):
704 def copymap(self):
715 return self._rustmap.copymap()
705 return self._rustmap.copymap()
716
706
717 def debug_iter(self, all):
707 def debug_iter(self, all):
718 """
708 """
719 Return an iterator of (filename, state, mode, size, mtime) tuples
709 Return an iterator of (filename, state, mode, size, mtime) tuples
720
710
721 `all`: also include with `state == b' '` dirstate tree nodes that
711 `all`: also include with `state == b' '` dirstate tree nodes that
722 don't have an associated `DirstateItem`.
712 don't have an associated `DirstateItem`.
723
713
724 """
714 """
725 return self._rustmap.debug_iter(all)
715 return self._rustmap.debug_iter(all)
726
716
727 def preload(self):
717 def preload(self):
728 self._rustmap
718 self._rustmap
729
719
730 def clear(self):
720 def clear(self):
731 self._rustmap.clear()
721 self._rustmap.clear()
732 self.setparents(
722 self.setparents(
733 self._nodeconstants.nullid, self._nodeconstants.nullid
723 self._nodeconstants.nullid, self._nodeconstants.nullid
734 )
724 )
735 util.clearcachedproperty(self, b"_dirs")
725 util.clearcachedproperty(self, b"_dirs")
736 util.clearcachedproperty(self, b"_alldirs")
726 util.clearcachedproperty(self, b"_alldirs")
737 util.clearcachedproperty(self, b"dirfoldmap")
727 util.clearcachedproperty(self, b"dirfoldmap")
738
728
739 def items(self):
729 def items(self):
740 return self._rustmap.items()
730 return self._rustmap.items()
741
731
742 def keys(self):
732 def keys(self):
743 return iter(self._rustmap)
733 return iter(self._rustmap)
744
734
745 def __contains__(self, key):
735 def __contains__(self, key):
746 return key in self._rustmap
736 return key in self._rustmap
747
737
748 def __getitem__(self, item):
738 def __getitem__(self, item):
749 return self._rustmap[item]
739 return self._rustmap[item]
750
740
751 def __len__(self):
741 def __len__(self):
752 return len(self._rustmap)
742 return len(self._rustmap)
753
743
754 def __iter__(self):
744 def __iter__(self):
755 return iter(self._rustmap)
745 return iter(self._rustmap)
756
746
757 # forward for python2,3 compat
747 # forward for python2,3 compat
758 iteritems = items
748 iteritems = items
759
749
760 def _opendirstatefile(self):
750 def _opendirstatefile(self):
761 fp, mode = txnutil.trypending(
751 fp, mode = txnutil.trypending(
762 self._root, self._opener, self._filename
752 self._root, self._opener, self._filename
763 )
753 )
764 if self._pendingmode is not None and self._pendingmode != mode:
754 if self._pendingmode is not None and self._pendingmode != mode:
765 fp.close()
755 fp.close()
766 raise error.Abort(
756 raise error.Abort(
767 _(b'working directory state may be changed parallelly')
757 _(b'working directory state may be changed parallelly')
768 )
758 )
769 self._pendingmode = mode
759 self._pendingmode = mode
770 return fp
760 return fp
771
761
772 def _readdirstatefile(self, size=-1):
762 def _readdirstatefile(self, size=-1):
773 try:
763 try:
774 with self._opendirstatefile() as fp:
764 with self._opendirstatefile() as fp:
775 return fp.read(size)
765 return fp.read(size)
776 except IOError as err:
766 except IOError as err:
777 if err.errno != errno.ENOENT:
767 if err.errno != errno.ENOENT:
778 raise
768 raise
779 # File doesn't exist, so the current state is empty
769 # File doesn't exist, so the current state is empty
780 return b''
770 return b''
781
771
782 def setparents(self, p1, p2):
772 def setparents(self, p1, p2):
783 self._parents = (p1, p2)
773 self._parents = (p1, p2)
784 self._dirtyparents = True
774 self._dirtyparents = True
785
775
786 def parents(self):
776 def parents(self):
787 if not self._parents:
777 if not self._parents:
788 if self._use_dirstate_v2:
778 if self._use_dirstate_v2:
789 self._parents = self.docket.parents
779 self._parents = self.docket.parents
790 else:
780 else:
791 read_len = self._nodelen * 2
781 read_len = self._nodelen * 2
792 st = self._readdirstatefile(read_len)
782 st = self._readdirstatefile(read_len)
793 l = len(st)
783 l = len(st)
794 if l == read_len:
784 if l == read_len:
795 self._parents = (
785 self._parents = (
796 st[: self._nodelen],
786 st[: self._nodelen],
797 st[self._nodelen : 2 * self._nodelen],
787 st[self._nodelen : 2 * self._nodelen],
798 )
788 )
799 elif l == 0:
789 elif l == 0:
800 self._parents = (
790 self._parents = (
801 self._nodeconstants.nullid,
791 self._nodeconstants.nullid,
802 self._nodeconstants.nullid,
792 self._nodeconstants.nullid,
803 )
793 )
804 else:
794 else:
805 raise error.Abort(
795 raise error.Abort(
806 _(b'working directory state appears damaged!')
796 _(b'working directory state appears damaged!')
807 )
797 )
808
798
809 return self._parents
799 return self._parents
810
800
811 @property
801 @property
812 def docket(self):
802 def docket(self):
813 if not self._docket:
803 if not self._docket:
814 if not self._use_dirstate_v2:
804 if not self._use_dirstate_v2:
815 raise error.ProgrammingError(
805 raise error.ProgrammingError(
816 b'dirstate only has a docket in v2 format'
806 b'dirstate only has a docket in v2 format'
817 )
807 )
818 self._docket = docketmod.DirstateDocket.parse(
808 self._docket = docketmod.DirstateDocket.parse(
819 self._readdirstatefile(), self._nodeconstants
809 self._readdirstatefile(), self._nodeconstants
820 )
810 )
821 return self._docket
811 return self._docket
822
812
823 @propertycache
813 @propertycache
824 def _rustmap(self):
814 def _rustmap(self):
825 """
815 """
826 Fills the Dirstatemap when called.
816 Fills the Dirstatemap when called.
827 """
817 """
828 # ignore HG_PENDING because identity is used only for writing
818 # ignore HG_PENDING because identity is used only for writing
829 self.identity = util.filestat.frompath(
819 self.identity = util.filestat.frompath(
830 self._opener.join(self._filename)
820 self._opener.join(self._filename)
831 )
821 )
832
822
833 if self._use_dirstate_v2:
823 if self._use_dirstate_v2:
834 if self.docket.uuid:
824 if self.docket.uuid:
835 # TODO: use mmap when possible
825 # TODO: use mmap when possible
836 data = self._opener.read(self.docket.data_filename())
826 data = self._opener.read(self.docket.data_filename())
837 else:
827 else:
838 data = b''
828 data = b''
839 self._rustmap = rustmod.DirstateMap.new_v2(
829 self._rustmap = rustmod.DirstateMap.new_v2(
840 data, self.docket.data_size, self.docket.tree_metadata
830 data, self.docket.data_size, self.docket.tree_metadata
841 )
831 )
842 parents = self.docket.parents
832 parents = self.docket.parents
843 else:
833 else:
844 self._rustmap, parents = rustmod.DirstateMap.new_v1(
834 self._rustmap, parents = rustmod.DirstateMap.new_v1(
845 self._use_dirstate_tree, self._readdirstatefile()
835 self._use_dirstate_tree, self._readdirstatefile()
846 )
836 )
847
837
848 if parents and not self._dirtyparents:
838 if parents and not self._dirtyparents:
849 self.setparents(*parents)
839 self.setparents(*parents)
850
840
851 self.__contains__ = self._rustmap.__contains__
841 self.__contains__ = self._rustmap.__contains__
852 self.__getitem__ = self._rustmap.__getitem__
842 self.__getitem__ = self._rustmap.__getitem__
853 self.get = self._rustmap.get
843 self.get = self._rustmap.get
854 return self._rustmap
844 return self._rustmap
855
845
856 def write(self, tr, st, now):
846 def write(self, tr, st, now):
857 if not self._use_dirstate_v2:
847 if not self._use_dirstate_v2:
858 p1, p2 = self.parents()
848 p1, p2 = self.parents()
859 packed = self._rustmap.write_v1(p1, p2, now)
849 packed = self._rustmap.write_v1(p1, p2, now)
860 st.write(packed)
850 st.write(packed)
861 st.close()
851 st.close()
862 self._dirtyparents = False
852 self._dirtyparents = False
863 return
853 return
864
854
865 # We can only append to an existing data file if there is one
855 # We can only append to an existing data file if there is one
866 can_append = self.docket.uuid is not None
856 can_append = self.docket.uuid is not None
867 packed, meta, append = self._rustmap.write_v2(now, can_append)
857 packed, meta, append = self._rustmap.write_v2(now, can_append)
868 if append:
858 if append:
869 docket = self.docket
859 docket = self.docket
870 data_filename = docket.data_filename()
860 data_filename = docket.data_filename()
871 if tr:
861 if tr:
872 tr.add(data_filename, docket.data_size)
862 tr.add(data_filename, docket.data_size)
873 with self._opener(data_filename, b'r+b') as fp:
863 with self._opener(data_filename, b'r+b') as fp:
874 fp.seek(docket.data_size)
864 fp.seek(docket.data_size)
875 assert fp.tell() == docket.data_size
865 assert fp.tell() == docket.data_size
876 written = fp.write(packed)
866 written = fp.write(packed)
877 if written is not None: # py2 may return None
867 if written is not None: # py2 may return None
878 assert written == len(packed), (written, len(packed))
868 assert written == len(packed), (written, len(packed))
879 docket.data_size += len(packed)
869 docket.data_size += len(packed)
880 docket.parents = self.parents()
870 docket.parents = self.parents()
881 docket.tree_metadata = meta
871 docket.tree_metadata = meta
882 st.write(docket.serialize())
872 st.write(docket.serialize())
883 st.close()
873 st.close()
884 else:
874 else:
885 old_docket = self.docket
875 old_docket = self.docket
886 new_docket = docketmod.DirstateDocket.with_new_uuid(
876 new_docket = docketmod.DirstateDocket.with_new_uuid(
887 self.parents(), len(packed), meta
877 self.parents(), len(packed), meta
888 )
878 )
889 data_filename = new_docket.data_filename()
879 data_filename = new_docket.data_filename()
890 if tr:
880 if tr:
891 tr.add(data_filename, 0)
881 tr.add(data_filename, 0)
892 self._opener.write(data_filename, packed)
882 self._opener.write(data_filename, packed)
893 # Write the new docket after the new data file has been
883 # Write the new docket after the new data file has been
894 # written. Because `st` was opened with `atomictemp=True`,
884 # written. Because `st` was opened with `atomictemp=True`,
895 # the actual `.hg/dirstate` file is only affected on close.
885 # the actual `.hg/dirstate` file is only affected on close.
896 st.write(new_docket.serialize())
886 st.write(new_docket.serialize())
897 st.close()
887 st.close()
898 # Remove the old data file after the new docket pointing to
888 # Remove the old data file after the new docket pointing to
899 # the new data file was written.
889 # the new data file was written.
900 if old_docket.uuid:
890 if old_docket.uuid:
901 data_filename = old_docket.data_filename()
891 data_filename = old_docket.data_filename()
902 unlink = lambda _tr=None: self._opener.unlink(data_filename)
892 unlink = lambda _tr=None: self._opener.unlink(data_filename)
903 if tr:
893 if tr:
904 category = b"dirstate-v2-clean-" + old_docket.uuid
894 category = b"dirstate-v2-clean-" + old_docket.uuid
905 tr.addpostclose(category, unlink)
895 tr.addpostclose(category, unlink)
906 else:
896 else:
907 unlink()
897 unlink()
908 self._docket = new_docket
898 self._docket = new_docket
909 # Reload from the newly-written file
899 # Reload from the newly-written file
910 util.clearcachedproperty(self, b"_rustmap")
900 util.clearcachedproperty(self, b"_rustmap")
911 self._dirtyparents = False
901 self._dirtyparents = False
912
902
913 @propertycache
903 @propertycache
914 def filefoldmap(self):
904 def filefoldmap(self):
915 """Returns a dictionary mapping normalized case paths to their
905 """Returns a dictionary mapping normalized case paths to their
916 non-normalized versions.
906 non-normalized versions.
917 """
907 """
918 return self._rustmap.filefoldmapasdict()
908 return self._rustmap.filefoldmapasdict()
919
909
920 def hastrackeddir(self, d):
910 def hastrackeddir(self, d):
921 return self._rustmap.hastrackeddir(d)
911 return self._rustmap.hastrackeddir(d)
922
912
923 def hasdir(self, d):
913 def hasdir(self, d):
924 return self._rustmap.hasdir(d)
914 return self._rustmap.hasdir(d)
925
915
926 @propertycache
916 @propertycache
927 def identity(self):
917 def identity(self):
928 self._rustmap
918 self._rustmap
929 return self.identity
919 return self.identity
930
920
931 @property
921 @property
932 def nonnormalset(self):
922 def nonnormalset(self):
933 nonnorm = self._rustmap.non_normal_entries()
923 nonnorm = self._rustmap.non_normal_entries()
934 return nonnorm
924 return nonnorm
935
925
936 @propertycache
926 @propertycache
937 def otherparentset(self):
927 def otherparentset(self):
938 otherparents = self._rustmap.other_parent_entries()
928 otherparents = self._rustmap.other_parent_entries()
939 return otherparents
929 return otherparents
940
930
941 def non_normal_or_other_parent_paths(self):
931 def non_normal_or_other_parent_paths(self):
942 return self._rustmap.non_normal_or_other_parent_paths()
932 return self._rustmap.non_normal_or_other_parent_paths()
943
933
944 @propertycache
934 @propertycache
945 def dirfoldmap(self):
935 def dirfoldmap(self):
946 f = {}
936 f = {}
947 normcase = util.normcase
937 normcase = util.normcase
948 for name in self._rustmap.tracked_dirs():
938 for name in self._rustmap.tracked_dirs():
949 f[normcase(name)] = name
939 f[normcase(name)] = name
950 return f
940 return f
951
941
952 def set_possibly_dirty(self, filename):
942 def set_possibly_dirty(self, filename):
953 """record that the current state of the file on disk is unknown"""
943 """record that the current state of the file on disk is unknown"""
954 entry = self[filename]
944 entry = self[filename]
955 entry.set_possibly_dirty()
945 entry.set_possibly_dirty()
956 self._rustmap.set_dirstate_item(filename, entry)
946 self._rustmap.set_dirstate_item(filename, entry)
957
947
958 def set_clean(self, filename, mode, size, mtime):
948 def set_clean(self, filename, mode, size, mtime):
959 """mark a file as back to a clean state"""
949 """mark a file as back to a clean state"""
960 entry = self[filename]
950 entry = self[filename]
961 mtime = mtime & rangemask
951 mtime = mtime & rangemask
962 size = size & rangemask
952 size = size & rangemask
963 entry.set_clean(mode, size, mtime)
953 entry.set_clean(mode, size, mtime)
964 self._rustmap.set_dirstate_item(filename, entry)
954 self._rustmap.set_dirstate_item(filename, entry)
965 self._rustmap.copymap().pop(filename, None)
955 self._rustmap.copymap().pop(filename, None)
966
956
967 def __setitem__(self, key, value):
957 def __setitem__(self, key, value):
968 assert isinstance(value, DirstateItem)
958 assert isinstance(value, DirstateItem)
969 self._rustmap.set_dirstate_item(key, value)
959 self._rustmap.set_dirstate_item(key, value)
@@ -1,430 +1,414 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 use crate::dirstate::parsers::Timestamp;
8 use crate::dirstate::parsers::Timestamp;
9 use crate::{
9 use crate::{
10 dirstate::EntryState,
10 dirstate::EntryState,
11 dirstate::SIZE_FROM_OTHER_PARENT,
11 dirstate::SIZE_FROM_OTHER_PARENT,
12 dirstate::SIZE_NON_NORMAL,
12 dirstate::SIZE_NON_NORMAL,
13 pack_dirstate, parse_dirstate,
13 pack_dirstate, parse_dirstate,
14 utils::hg_path::{HgPath, HgPathBuf},
14 utils::hg_path::{HgPath, HgPathBuf},
15 CopyMap, DirsMultiset, DirstateEntry, DirstateError, DirstateParents,
15 CopyMap, DirsMultiset, DirstateEntry, DirstateError, DirstateParents,
16 StateMap,
16 StateMap,
17 };
17 };
18 use micro_timer::timed;
18 use micro_timer::timed;
19 use std::collections::HashSet;
19 use std::collections::HashSet;
20 use std::iter::FromIterator;
20 use std::iter::FromIterator;
21 use std::ops::Deref;
21 use std::ops::Deref;
22
22
23 #[derive(Default)]
23 #[derive(Default)]
24 pub struct DirstateMap {
24 pub struct DirstateMap {
25 state_map: StateMap,
25 state_map: StateMap,
26 pub copy_map: CopyMap,
26 pub copy_map: CopyMap,
27 pub dirs: Option<DirsMultiset>,
27 pub dirs: Option<DirsMultiset>,
28 pub all_dirs: Option<DirsMultiset>,
28 pub all_dirs: Option<DirsMultiset>,
29 non_normal_set: Option<HashSet<HgPathBuf>>,
29 non_normal_set: Option<HashSet<HgPathBuf>>,
30 other_parent_set: Option<HashSet<HgPathBuf>>,
30 other_parent_set: Option<HashSet<HgPathBuf>>,
31 }
31 }
32
32
33 /// Should only really be used in python interface code, for clarity
33 /// Should only really be used in python interface code, for clarity
34 impl Deref for DirstateMap {
34 impl Deref for DirstateMap {
35 type Target = StateMap;
35 type Target = StateMap;
36
36
37 fn deref(&self) -> &Self::Target {
37 fn deref(&self) -> &Self::Target {
38 &self.state_map
38 &self.state_map
39 }
39 }
40 }
40 }
41
41
42 impl FromIterator<(HgPathBuf, DirstateEntry)> for DirstateMap {
42 impl FromIterator<(HgPathBuf, DirstateEntry)> for DirstateMap {
43 fn from_iter<I: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>(
43 fn from_iter<I: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>(
44 iter: I,
44 iter: I,
45 ) -> Self {
45 ) -> Self {
46 Self {
46 Self {
47 state_map: iter.into_iter().collect(),
47 state_map: iter.into_iter().collect(),
48 ..Self::default()
48 ..Self::default()
49 }
49 }
50 }
50 }
51 }
51 }
52
52
53 impl DirstateMap {
53 impl DirstateMap {
54 pub fn new() -> Self {
54 pub fn new() -> Self {
55 Self::default()
55 Self::default()
56 }
56 }
57
57
58 pub fn clear(&mut self) {
58 pub fn clear(&mut self) {
59 self.state_map = StateMap::default();
59 self.state_map = StateMap::default();
60 self.copy_map.clear();
60 self.copy_map.clear();
61 self.non_normal_set = None;
61 self.non_normal_set = None;
62 self.other_parent_set = None;
62 self.other_parent_set = None;
63 }
63 }
64
64
65 pub fn set_entry(&mut self, filename: &HgPath, entry: DirstateEntry) {
65 pub fn set_entry(&mut self, filename: &HgPath, entry: DirstateEntry) {
66 self.state_map.insert(filename.to_owned(), entry);
66 self.state_map.insert(filename.to_owned(), entry);
67 }
67 }
68
68
69 /// Add a tracked file to the dirstate
69 /// Add a tracked file to the dirstate
70 pub fn add_file(
70 pub fn add_file(
71 &mut self,
71 &mut self,
72 filename: &HgPath,
72 filename: &HgPath,
73 entry: DirstateEntry,
73 entry: DirstateEntry,
74 ) -> Result<(), DirstateError> {
74 ) -> Result<(), DirstateError> {
75 let old_state = self.get(filename).map(|e| e.state());
75 let old_state = self.get(filename).map(|e| e.state());
76 if old_state.is_none() || old_state == Some(EntryState::Removed) {
76 if old_state.is_none() || old_state == Some(EntryState::Removed) {
77 if let Some(ref mut dirs) = self.dirs {
77 if let Some(ref mut dirs) = self.dirs {
78 dirs.add_path(filename)?;
78 dirs.add_path(filename)?;
79 }
79 }
80 }
80 }
81 if old_state.is_none() {
81 if old_state.is_none() {
82 if let Some(ref mut all_dirs) = self.all_dirs {
82 if let Some(ref mut all_dirs) = self.all_dirs {
83 all_dirs.add_path(filename)?;
83 all_dirs.add_path(filename)?;
84 }
84 }
85 }
85 }
86 self.state_map.insert(filename.to_owned(), entry.to_owned());
86 self.state_map.insert(filename.to_owned(), entry.to_owned());
87
87
88 if entry.is_non_normal() {
88 if entry.is_non_normal() {
89 self.get_non_normal_other_parent_entries()
89 self.get_non_normal_other_parent_entries()
90 .0
90 .0
91 .insert(filename.to_owned());
91 .insert(filename.to_owned());
92 }
92 }
93
93
94 if entry.is_from_other_parent() {
94 if entry.is_from_other_parent() {
95 self.get_non_normal_other_parent_entries()
95 self.get_non_normal_other_parent_entries()
96 .1
96 .1
97 .insert(filename.to_owned());
97 .insert(filename.to_owned());
98 }
98 }
99 Ok(())
99 Ok(())
100 }
100 }
101
101
102 /// Mark a file as removed in the dirstate.
102 /// Mark a file as removed in the dirstate.
103 ///
103 ///
104 /// The `size` parameter is used to store sentinel values that indicate
104 /// The `size` parameter is used to store sentinel values that indicate
105 /// the file's previous state. In the future, we should refactor this
105 /// the file's previous state. In the future, we should refactor this
106 /// to be more explicit about what that state is.
106 /// to be more explicit about what that state is.
107 pub fn remove_file(
107 pub fn remove_file(
108 &mut self,
108 &mut self,
109 filename: &HgPath,
109 filename: &HgPath,
110 in_merge: bool,
110 in_merge: bool,
111 ) -> Result<(), DirstateError> {
111 ) -> Result<(), DirstateError> {
112 let old_entry_opt = self.get(filename);
112 let old_entry_opt = self.get(filename);
113 let old_state = old_entry_opt.map(|e| e.state());
113 let old_state = old_entry_opt.map(|e| e.state());
114 let mut size = 0;
114 let mut size = 0;
115 if in_merge {
115 if in_merge {
116 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
116 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
117 // during a merge. So I (marmoute) am not sure we need the
117 // during a merge. So I (marmoute) am not sure we need the
118 // conditionnal at all. Adding double checking this with assert
118 // conditionnal at all. Adding double checking this with assert
119 // would be nice.
119 // would be nice.
120 if let Some(old_entry) = old_entry_opt {
120 if let Some(old_entry) = old_entry_opt {
121 // backup the previous state
121 // backup the previous state
122 if old_entry.state() == EntryState::Merged {
122 if old_entry.state() == EntryState::Merged {
123 size = SIZE_NON_NORMAL;
123 size = SIZE_NON_NORMAL;
124 } else if old_entry.state() == EntryState::Normal
124 } else if old_entry.state() == EntryState::Normal
125 && old_entry.size() == SIZE_FROM_OTHER_PARENT
125 && old_entry.size() == SIZE_FROM_OTHER_PARENT
126 {
126 {
127 // other parent
127 // other parent
128 size = SIZE_FROM_OTHER_PARENT;
128 size = SIZE_FROM_OTHER_PARENT;
129 self.get_non_normal_other_parent_entries()
129 self.get_non_normal_other_parent_entries()
130 .1
130 .1
131 .insert(filename.to_owned());
131 .insert(filename.to_owned());
132 }
132 }
133 }
133 }
134 }
134 }
135 if old_state.is_some() && old_state != Some(EntryState::Removed) {
135 if old_state.is_some() && old_state != Some(EntryState::Removed) {
136 if let Some(ref mut dirs) = self.dirs {
136 if let Some(ref mut dirs) = self.dirs {
137 dirs.delete_path(filename)?;
137 dirs.delete_path(filename)?;
138 }
138 }
139 }
139 }
140 if old_state.is_none() {
140 if old_state.is_none() {
141 if let Some(ref mut all_dirs) = self.all_dirs {
141 if let Some(ref mut all_dirs) = self.all_dirs {
142 all_dirs.add_path(filename)?;
142 all_dirs.add_path(filename)?;
143 }
143 }
144 }
144 }
145 if size == 0 {
145 if size == 0 {
146 self.copy_map.remove(filename);
146 self.copy_map.remove(filename);
147 }
147 }
148
148
149 self.state_map
149 self.state_map
150 .insert(filename.to_owned(), DirstateEntry::new_removed(size));
150 .insert(filename.to_owned(), DirstateEntry::new_removed(size));
151 self.get_non_normal_other_parent_entries()
151 self.get_non_normal_other_parent_entries()
152 .0
152 .0
153 .insert(filename.to_owned());
153 .insert(filename.to_owned());
154 Ok(())
154 Ok(())
155 }
155 }
156
156
157 /// Remove a file from the dirstate.
157 /// Remove a file from the dirstate.
158 /// Returns `true` if the file was previously recorded.
158 /// Returns `true` if the file was previously recorded.
159 pub fn drop_entry_and_copy_source(
159 pub fn drop_entry_and_copy_source(
160 &mut self,
160 &mut self,
161 filename: &HgPath,
161 filename: &HgPath,
162 ) -> Result<(), DirstateError> {
162 ) -> Result<(), DirstateError> {
163 let old_state = self.get(filename).map(|e| e.state());
163 let old_state = self.get(filename).map(|e| e.state());
164 let exists = self.state_map.remove(filename).is_some();
164 let exists = self.state_map.remove(filename).is_some();
165
165
166 if exists {
166 if exists {
167 if old_state != Some(EntryState::Removed) {
167 if old_state != Some(EntryState::Removed) {
168 if let Some(ref mut dirs) = self.dirs {
168 if let Some(ref mut dirs) = self.dirs {
169 dirs.delete_path(filename)?;
169 dirs.delete_path(filename)?;
170 }
170 }
171 }
171 }
172 if let Some(ref mut all_dirs) = self.all_dirs {
172 if let Some(ref mut all_dirs) = self.all_dirs {
173 all_dirs.delete_path(filename)?;
173 all_dirs.delete_path(filename)?;
174 }
174 }
175 }
175 }
176 self.get_non_normal_other_parent_entries()
176 self.get_non_normal_other_parent_entries()
177 .0
177 .0
178 .remove(filename);
178 .remove(filename);
179
179
180 self.copy_map.remove(filename);
180 self.copy_map.remove(filename);
181
181
182 Ok(())
182 Ok(())
183 }
183 }
184
184
185 pub fn clear_ambiguous_times(
186 &mut self,
187 filenames: Vec<HgPathBuf>,
188 now: i32,
189 ) {
190 for filename in filenames {
191 if let Some(entry) = self.state_map.get_mut(&filename) {
192 if entry.clear_ambiguous_mtime(now) {
193 self.get_non_normal_other_parent_entries()
194 .0
195 .insert(filename.to_owned());
196 }
197 }
198 }
199 }
200
201 pub fn non_normal_entries_remove(
185 pub fn non_normal_entries_remove(
202 &mut self,
186 &mut self,
203 key: impl AsRef<HgPath>,
187 key: impl AsRef<HgPath>,
204 ) -> bool {
188 ) -> bool {
205 self.get_non_normal_other_parent_entries()
189 self.get_non_normal_other_parent_entries()
206 .0
190 .0
207 .remove(key.as_ref())
191 .remove(key.as_ref())
208 }
192 }
209
193
210 pub fn non_normal_entries_add(&mut self, key: impl AsRef<HgPath>) {
194 pub fn non_normal_entries_add(&mut self, key: impl AsRef<HgPath>) {
211 self.get_non_normal_other_parent_entries()
195 self.get_non_normal_other_parent_entries()
212 .0
196 .0
213 .insert(key.as_ref().into());
197 .insert(key.as_ref().into());
214 }
198 }
215
199
216 pub fn non_normal_entries_union(
200 pub fn non_normal_entries_union(
217 &mut self,
201 &mut self,
218 other: HashSet<HgPathBuf>,
202 other: HashSet<HgPathBuf>,
219 ) -> Vec<HgPathBuf> {
203 ) -> Vec<HgPathBuf> {
220 self.get_non_normal_other_parent_entries()
204 self.get_non_normal_other_parent_entries()
221 .0
205 .0
222 .union(&other)
206 .union(&other)
223 .map(ToOwned::to_owned)
207 .map(ToOwned::to_owned)
224 .collect()
208 .collect()
225 }
209 }
226
210
227 pub fn get_non_normal_other_parent_entries(
211 pub fn get_non_normal_other_parent_entries(
228 &mut self,
212 &mut self,
229 ) -> (&mut HashSet<HgPathBuf>, &mut HashSet<HgPathBuf>) {
213 ) -> (&mut HashSet<HgPathBuf>, &mut HashSet<HgPathBuf>) {
230 self.set_non_normal_other_parent_entries(false);
214 self.set_non_normal_other_parent_entries(false);
231 (
215 (
232 self.non_normal_set.as_mut().unwrap(),
216 self.non_normal_set.as_mut().unwrap(),
233 self.other_parent_set.as_mut().unwrap(),
217 self.other_parent_set.as_mut().unwrap(),
234 )
218 )
235 }
219 }
236
220
237 /// Useful to get immutable references to those sets in contexts where
221 /// Useful to get immutable references to those sets in contexts where
238 /// you only have an immutable reference to the `DirstateMap`, like when
222 /// you only have an immutable reference to the `DirstateMap`, like when
239 /// sharing references with Python.
223 /// sharing references with Python.
240 ///
224 ///
241 /// TODO, get rid of this along with the other "setter/getter" stuff when
225 /// TODO, get rid of this along with the other "setter/getter" stuff when
242 /// a nice typestate plan is defined.
226 /// a nice typestate plan is defined.
243 ///
227 ///
244 /// # Panics
228 /// # Panics
245 ///
229 ///
246 /// Will panic if either set is `None`.
230 /// Will panic if either set is `None`.
247 pub fn get_non_normal_other_parent_entries_panic(
231 pub fn get_non_normal_other_parent_entries_panic(
248 &self,
232 &self,
249 ) -> (&HashSet<HgPathBuf>, &HashSet<HgPathBuf>) {
233 ) -> (&HashSet<HgPathBuf>, &HashSet<HgPathBuf>) {
250 (
234 (
251 self.non_normal_set.as_ref().unwrap(),
235 self.non_normal_set.as_ref().unwrap(),
252 self.other_parent_set.as_ref().unwrap(),
236 self.other_parent_set.as_ref().unwrap(),
253 )
237 )
254 }
238 }
255
239
256 pub fn set_non_normal_other_parent_entries(&mut self, force: bool) {
240 pub fn set_non_normal_other_parent_entries(&mut self, force: bool) {
257 if !force
241 if !force
258 && self.non_normal_set.is_some()
242 && self.non_normal_set.is_some()
259 && self.other_parent_set.is_some()
243 && self.other_parent_set.is_some()
260 {
244 {
261 return;
245 return;
262 }
246 }
263 let mut non_normal = HashSet::new();
247 let mut non_normal = HashSet::new();
264 let mut other_parent = HashSet::new();
248 let mut other_parent = HashSet::new();
265
249
266 for (filename, entry) in self.state_map.iter() {
250 for (filename, entry) in self.state_map.iter() {
267 if entry.is_non_normal() {
251 if entry.is_non_normal() {
268 non_normal.insert(filename.to_owned());
252 non_normal.insert(filename.to_owned());
269 }
253 }
270 if entry.is_from_other_parent() {
254 if entry.is_from_other_parent() {
271 other_parent.insert(filename.to_owned());
255 other_parent.insert(filename.to_owned());
272 }
256 }
273 }
257 }
274 self.non_normal_set = Some(non_normal);
258 self.non_normal_set = Some(non_normal);
275 self.other_parent_set = Some(other_parent);
259 self.other_parent_set = Some(other_parent);
276 }
260 }
277
261
278 /// Both of these setters and their uses appear to be the simplest way to
262 /// Both of these setters and their uses appear to be the simplest way to
279 /// emulate a Python lazy property, but it is ugly and unidiomatic.
263 /// emulate a Python lazy property, but it is ugly and unidiomatic.
280 /// TODO One day, rewriting this struct using the typestate might be a
264 /// TODO One day, rewriting this struct using the typestate might be a
281 /// good idea.
265 /// good idea.
282 pub fn set_all_dirs(&mut self) -> Result<(), DirstateError> {
266 pub fn set_all_dirs(&mut self) -> Result<(), DirstateError> {
283 if self.all_dirs.is_none() {
267 if self.all_dirs.is_none() {
284 self.all_dirs = Some(DirsMultiset::from_dirstate(
268 self.all_dirs = Some(DirsMultiset::from_dirstate(
285 self.state_map.iter().map(|(k, v)| Ok((k, *v))),
269 self.state_map.iter().map(|(k, v)| Ok((k, *v))),
286 false,
270 false,
287 )?);
271 )?);
288 }
272 }
289 Ok(())
273 Ok(())
290 }
274 }
291
275
292 pub fn set_dirs(&mut self) -> Result<(), DirstateError> {
276 pub fn set_dirs(&mut self) -> Result<(), DirstateError> {
293 if self.dirs.is_none() {
277 if self.dirs.is_none() {
294 self.dirs = Some(DirsMultiset::from_dirstate(
278 self.dirs = Some(DirsMultiset::from_dirstate(
295 self.state_map.iter().map(|(k, v)| Ok((k, *v))),
279 self.state_map.iter().map(|(k, v)| Ok((k, *v))),
296 true,
280 true,
297 )?);
281 )?);
298 }
282 }
299 Ok(())
283 Ok(())
300 }
284 }
301
285
302 pub fn has_tracked_dir(
286 pub fn has_tracked_dir(
303 &mut self,
287 &mut self,
304 directory: &HgPath,
288 directory: &HgPath,
305 ) -> Result<bool, DirstateError> {
289 ) -> Result<bool, DirstateError> {
306 self.set_dirs()?;
290 self.set_dirs()?;
307 Ok(self.dirs.as_ref().unwrap().contains(directory))
291 Ok(self.dirs.as_ref().unwrap().contains(directory))
308 }
292 }
309
293
310 pub fn has_dir(
294 pub fn has_dir(
311 &mut self,
295 &mut self,
312 directory: &HgPath,
296 directory: &HgPath,
313 ) -> Result<bool, DirstateError> {
297 ) -> Result<bool, DirstateError> {
314 self.set_all_dirs()?;
298 self.set_all_dirs()?;
315 Ok(self.all_dirs.as_ref().unwrap().contains(directory))
299 Ok(self.all_dirs.as_ref().unwrap().contains(directory))
316 }
300 }
317
301
318 #[timed]
302 #[timed]
319 pub fn read(
303 pub fn read(
320 &mut self,
304 &mut self,
321 file_contents: &[u8],
305 file_contents: &[u8],
322 ) -> Result<Option<DirstateParents>, DirstateError> {
306 ) -> Result<Option<DirstateParents>, DirstateError> {
323 if file_contents.is_empty() {
307 if file_contents.is_empty() {
324 return Ok(None);
308 return Ok(None);
325 }
309 }
326
310
327 let (parents, entries, copies) = parse_dirstate(file_contents)?;
311 let (parents, entries, copies) = parse_dirstate(file_contents)?;
328 self.state_map.extend(
312 self.state_map.extend(
329 entries
313 entries
330 .into_iter()
314 .into_iter()
331 .map(|(path, entry)| (path.to_owned(), entry)),
315 .map(|(path, entry)| (path.to_owned(), entry)),
332 );
316 );
333 self.copy_map.extend(
317 self.copy_map.extend(
334 copies
318 copies
335 .into_iter()
319 .into_iter()
336 .map(|(path, copy)| (path.to_owned(), copy.to_owned())),
320 .map(|(path, copy)| (path.to_owned(), copy.to_owned())),
337 );
321 );
338 Ok(Some(parents.clone()))
322 Ok(Some(parents.clone()))
339 }
323 }
340
324
341 pub fn pack(
325 pub fn pack(
342 &mut self,
326 &mut self,
343 parents: DirstateParents,
327 parents: DirstateParents,
344 now: Timestamp,
328 now: Timestamp,
345 ) -> Result<Vec<u8>, DirstateError> {
329 ) -> Result<Vec<u8>, DirstateError> {
346 let packed =
330 let packed =
347 pack_dirstate(&mut self.state_map, &self.copy_map, parents, now)?;
331 pack_dirstate(&mut self.state_map, &self.copy_map, parents, now)?;
348
332
349 self.set_non_normal_other_parent_entries(true);
333 self.set_non_normal_other_parent_entries(true);
350 Ok(packed)
334 Ok(packed)
351 }
335 }
352 }
336 }
353
337
354 #[cfg(test)]
338 #[cfg(test)]
355 mod tests {
339 mod tests {
356 use super::*;
340 use super::*;
357
341
358 #[test]
342 #[test]
359 fn test_dirs_multiset() {
343 fn test_dirs_multiset() {
360 let mut map = DirstateMap::new();
344 let mut map = DirstateMap::new();
361 assert!(map.dirs.is_none());
345 assert!(map.dirs.is_none());
362 assert!(map.all_dirs.is_none());
346 assert!(map.all_dirs.is_none());
363
347
364 assert_eq!(map.has_dir(HgPath::new(b"nope")).unwrap(), false);
348 assert_eq!(map.has_dir(HgPath::new(b"nope")).unwrap(), false);
365 assert!(map.all_dirs.is_some());
349 assert!(map.all_dirs.is_some());
366 assert!(map.dirs.is_none());
350 assert!(map.dirs.is_none());
367
351
368 assert_eq!(map.has_tracked_dir(HgPath::new(b"nope")).unwrap(), false);
352 assert_eq!(map.has_tracked_dir(HgPath::new(b"nope")).unwrap(), false);
369 assert!(map.dirs.is_some());
353 assert!(map.dirs.is_some());
370 }
354 }
371
355
372 #[test]
356 #[test]
373 fn test_add_file() {
357 fn test_add_file() {
374 let mut map = DirstateMap::new();
358 let mut map = DirstateMap::new();
375
359
376 assert_eq!(0, map.len());
360 assert_eq!(0, map.len());
377
361
378 map.add_file(
362 map.add_file(
379 HgPath::new(b"meh"),
363 HgPath::new(b"meh"),
380 DirstateEntry::from_v1_data(EntryState::Normal, 1337, 1337, 1337),
364 DirstateEntry::from_v1_data(EntryState::Normal, 1337, 1337, 1337),
381 )
365 )
382 .unwrap();
366 .unwrap();
383
367
384 assert_eq!(1, map.len());
368 assert_eq!(1, map.len());
385 assert_eq!(0, map.get_non_normal_other_parent_entries().0.len());
369 assert_eq!(0, map.get_non_normal_other_parent_entries().0.len());
386 assert_eq!(0, map.get_non_normal_other_parent_entries().1.len());
370 assert_eq!(0, map.get_non_normal_other_parent_entries().1.len());
387 }
371 }
388
372
389 #[test]
373 #[test]
390 fn test_non_normal_other_parent_entries() {
374 fn test_non_normal_other_parent_entries() {
391 let mut map: DirstateMap = [
375 let mut map: DirstateMap = [
392 (b"f1", (EntryState::Removed, 1337, 1337, 1337)),
376 (b"f1", (EntryState::Removed, 1337, 1337, 1337)),
393 (b"f2", (EntryState::Normal, 1337, 1337, -1)),
377 (b"f2", (EntryState::Normal, 1337, 1337, -1)),
394 (b"f3", (EntryState::Normal, 1337, 1337, 1337)),
378 (b"f3", (EntryState::Normal, 1337, 1337, 1337)),
395 (b"f4", (EntryState::Normal, 1337, -2, 1337)),
379 (b"f4", (EntryState::Normal, 1337, -2, 1337)),
396 (b"f5", (EntryState::Added, 1337, 1337, 1337)),
380 (b"f5", (EntryState::Added, 1337, 1337, 1337)),
397 (b"f6", (EntryState::Added, 1337, 1337, -1)),
381 (b"f6", (EntryState::Added, 1337, 1337, -1)),
398 (b"f7", (EntryState::Merged, 1337, 1337, -1)),
382 (b"f7", (EntryState::Merged, 1337, 1337, -1)),
399 (b"f8", (EntryState::Merged, 1337, 1337, 1337)),
383 (b"f8", (EntryState::Merged, 1337, 1337, 1337)),
400 (b"f9", (EntryState::Merged, 1337, -2, 1337)),
384 (b"f9", (EntryState::Merged, 1337, -2, 1337)),
401 (b"fa", (EntryState::Added, 1337, -2, 1337)),
385 (b"fa", (EntryState::Added, 1337, -2, 1337)),
402 (b"fb", (EntryState::Removed, 1337, -2, 1337)),
386 (b"fb", (EntryState::Removed, 1337, -2, 1337)),
403 ]
387 ]
404 .iter()
388 .iter()
405 .map(|(fname, (state, mode, size, mtime))| {
389 .map(|(fname, (state, mode, size, mtime))| {
406 (
390 (
407 HgPathBuf::from_bytes(fname.as_ref()),
391 HgPathBuf::from_bytes(fname.as_ref()),
408 DirstateEntry::from_v1_data(*state, *mode, *size, *mtime),
392 DirstateEntry::from_v1_data(*state, *mode, *size, *mtime),
409 )
393 )
410 })
394 })
411 .collect();
395 .collect();
412
396
413 let mut non_normal = [
397 let mut non_normal = [
414 b"f1", b"f2", b"f4", b"f5", b"f6", b"f7", b"f8", b"f9", b"fa",
398 b"f1", b"f2", b"f4", b"f5", b"f6", b"f7", b"f8", b"f9", b"fa",
415 b"fb",
399 b"fb",
416 ]
400 ]
417 .iter()
401 .iter()
418 .map(|x| HgPathBuf::from_bytes(x.as_ref()))
402 .map(|x| HgPathBuf::from_bytes(x.as_ref()))
419 .collect();
403 .collect();
420
404
421 let mut other_parent = HashSet::new();
405 let mut other_parent = HashSet::new();
422 other_parent.insert(HgPathBuf::from_bytes(b"f4"));
406 other_parent.insert(HgPathBuf::from_bytes(b"f4"));
423 let entries = map.get_non_normal_other_parent_entries();
407 let entries = map.get_non_normal_other_parent_entries();
424
408
425 assert_eq!(
409 assert_eq!(
426 (&mut non_normal, &mut other_parent),
410 (&mut non_normal, &mut other_parent),
427 (entries.0, entries.1)
411 (entries.0, entries.1)
428 );
412 );
429 }
413 }
430 }
414 }
@@ -1,1272 +1,1252 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::convert::TryInto;
4 use std::convert::TryInto;
5 use std::path::PathBuf;
5 use std::path::PathBuf;
6
6
7 use super::on_disk;
7 use super::on_disk;
8 use super::on_disk::DirstateV2ParseError;
8 use super::on_disk::DirstateV2ParseError;
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::parsers::Timestamp;
13 use crate::dirstate::parsers::Timestamp;
14 use crate::dirstate::SIZE_FROM_OTHER_PARENT;
14 use crate::dirstate::SIZE_FROM_OTHER_PARENT;
15 use crate::dirstate::SIZE_NON_NORMAL;
15 use crate::dirstate::SIZE_NON_NORMAL;
16 use crate::matchers::Matcher;
16 use crate::matchers::Matcher;
17 use crate::utils::hg_path::{HgPath, HgPathBuf};
17 use crate::utils::hg_path::{HgPath, HgPathBuf};
18 use crate::CopyMapIter;
18 use crate::CopyMapIter;
19 use crate::DirstateEntry;
19 use crate::DirstateEntry;
20 use crate::DirstateError;
20 use crate::DirstateError;
21 use crate::DirstateParents;
21 use crate::DirstateParents;
22 use crate::DirstateStatus;
22 use crate::DirstateStatus;
23 use crate::EntryState;
23 use crate::EntryState;
24 use crate::FastHashMap;
24 use crate::FastHashMap;
25 use crate::PatternFileWarning;
25 use crate::PatternFileWarning;
26 use crate::StateMapIter;
26 use crate::StateMapIter;
27 use crate::StatusError;
27 use crate::StatusError;
28 use crate::StatusOptions;
28 use crate::StatusOptions;
29
29
30 /// Append to an existing data file if the amount of unreachable data (not used
30 /// Append to an existing data file if the amount of unreachable data (not used
31 /// anymore) is less than this fraction of the total amount of existing data.
31 /// anymore) is less than this fraction of the total amount of existing data.
32 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
32 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
33
33
34 pub struct DirstateMap<'on_disk> {
34 pub struct DirstateMap<'on_disk> {
35 /// Contents of the `.hg/dirstate` file
35 /// Contents of the `.hg/dirstate` file
36 pub(super) on_disk: &'on_disk [u8],
36 pub(super) on_disk: &'on_disk [u8],
37
37
38 pub(super) root: ChildNodes<'on_disk>,
38 pub(super) root: ChildNodes<'on_disk>,
39
39
40 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
40 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
41 pub(super) nodes_with_entry_count: u32,
41 pub(super) nodes_with_entry_count: u32,
42
42
43 /// Number of nodes anywhere in the tree that have
43 /// Number of nodes anywhere in the tree that have
44 /// `.copy_source.is_some()`.
44 /// `.copy_source.is_some()`.
45 pub(super) nodes_with_copy_source_count: u32,
45 pub(super) nodes_with_copy_source_count: u32,
46
46
47 /// See on_disk::Header
47 /// See on_disk::Header
48 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
48 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
49
49
50 /// How many bytes of `on_disk` are not used anymore
50 /// How many bytes of `on_disk` are not used anymore
51 pub(super) unreachable_bytes: u32,
51 pub(super) unreachable_bytes: u32,
52 }
52 }
53
53
54 /// Using a plain `HgPathBuf` of the full path from the repository root as a
54 /// Using a plain `HgPathBuf` of the full path from the repository root as a
55 /// map key would also work: all paths in a given map have the same parent
55 /// map key would also work: all paths in a given map have the same parent
56 /// path, so comparing full paths gives the same result as comparing base
56 /// path, so comparing full paths gives the same result as comparing base
57 /// names. However `HashMap` would waste time always re-hashing the same
57 /// names. However `HashMap` would waste time always re-hashing the same
58 /// string prefix.
58 /// string prefix.
59 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
59 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
60
60
61 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
61 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
62 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
62 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
63 pub(super) enum BorrowedPath<'tree, 'on_disk> {
63 pub(super) enum BorrowedPath<'tree, 'on_disk> {
64 InMemory(&'tree HgPathBuf),
64 InMemory(&'tree HgPathBuf),
65 OnDisk(&'on_disk HgPath),
65 OnDisk(&'on_disk HgPath),
66 }
66 }
67
67
68 pub(super) enum ChildNodes<'on_disk> {
68 pub(super) enum ChildNodes<'on_disk> {
69 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
69 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
70 OnDisk(&'on_disk [on_disk::Node]),
70 OnDisk(&'on_disk [on_disk::Node]),
71 }
71 }
72
72
73 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
73 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
74 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
74 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
75 OnDisk(&'on_disk [on_disk::Node]),
75 OnDisk(&'on_disk [on_disk::Node]),
76 }
76 }
77
77
78 pub(super) enum NodeRef<'tree, 'on_disk> {
78 pub(super) enum NodeRef<'tree, 'on_disk> {
79 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
79 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
80 OnDisk(&'on_disk on_disk::Node),
80 OnDisk(&'on_disk on_disk::Node),
81 }
81 }
82
82
83 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
83 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
84 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
84 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
85 match *self {
85 match *self {
86 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
86 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
87 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
87 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
88 }
88 }
89 }
89 }
90 }
90 }
91
91
92 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
92 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
93 type Target = HgPath;
93 type Target = HgPath;
94
94
95 fn deref(&self) -> &HgPath {
95 fn deref(&self) -> &HgPath {
96 match *self {
96 match *self {
97 BorrowedPath::InMemory(in_memory) => in_memory,
97 BorrowedPath::InMemory(in_memory) => in_memory,
98 BorrowedPath::OnDisk(on_disk) => on_disk,
98 BorrowedPath::OnDisk(on_disk) => on_disk,
99 }
99 }
100 }
100 }
101 }
101 }
102
102
103 impl Default for ChildNodes<'_> {
103 impl Default for ChildNodes<'_> {
104 fn default() -> Self {
104 fn default() -> Self {
105 ChildNodes::InMemory(Default::default())
105 ChildNodes::InMemory(Default::default())
106 }
106 }
107 }
107 }
108
108
109 impl<'on_disk> ChildNodes<'on_disk> {
109 impl<'on_disk> ChildNodes<'on_disk> {
110 pub(super) fn as_ref<'tree>(
110 pub(super) fn as_ref<'tree>(
111 &'tree self,
111 &'tree self,
112 ) -> ChildNodesRef<'tree, 'on_disk> {
112 ) -> ChildNodesRef<'tree, 'on_disk> {
113 match self {
113 match self {
114 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
114 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
115 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
115 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
116 }
116 }
117 }
117 }
118
118
119 pub(super) fn is_empty(&self) -> bool {
119 pub(super) fn is_empty(&self) -> bool {
120 match self {
120 match self {
121 ChildNodes::InMemory(nodes) => nodes.is_empty(),
121 ChildNodes::InMemory(nodes) => nodes.is_empty(),
122 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
122 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
123 }
123 }
124 }
124 }
125
125
126 fn make_mut(
126 fn make_mut(
127 &mut self,
127 &mut self,
128 on_disk: &'on_disk [u8],
128 on_disk: &'on_disk [u8],
129 unreachable_bytes: &mut u32,
129 unreachable_bytes: &mut u32,
130 ) -> Result<
130 ) -> Result<
131 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
131 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
132 DirstateV2ParseError,
132 DirstateV2ParseError,
133 > {
133 > {
134 match self {
134 match self {
135 ChildNodes::InMemory(nodes) => Ok(nodes),
135 ChildNodes::InMemory(nodes) => Ok(nodes),
136 ChildNodes::OnDisk(nodes) => {
136 ChildNodes::OnDisk(nodes) => {
137 *unreachable_bytes +=
137 *unreachable_bytes +=
138 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
138 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
139 let nodes = nodes
139 let nodes = nodes
140 .iter()
140 .iter()
141 .map(|node| {
141 .map(|node| {
142 Ok((
142 Ok((
143 node.path(on_disk)?,
143 node.path(on_disk)?,
144 node.to_in_memory_node(on_disk)?,
144 node.to_in_memory_node(on_disk)?,
145 ))
145 ))
146 })
146 })
147 .collect::<Result<_, _>>()?;
147 .collect::<Result<_, _>>()?;
148 *self = ChildNodes::InMemory(nodes);
148 *self = ChildNodes::InMemory(nodes);
149 match self {
149 match self {
150 ChildNodes::InMemory(nodes) => Ok(nodes),
150 ChildNodes::InMemory(nodes) => Ok(nodes),
151 ChildNodes::OnDisk(_) => unreachable!(),
151 ChildNodes::OnDisk(_) => unreachable!(),
152 }
152 }
153 }
153 }
154 }
154 }
155 }
155 }
156 }
156 }
157
157
158 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
158 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
159 pub(super) fn get(
159 pub(super) fn get(
160 &self,
160 &self,
161 base_name: &HgPath,
161 base_name: &HgPath,
162 on_disk: &'on_disk [u8],
162 on_disk: &'on_disk [u8],
163 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
163 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
164 match self {
164 match self {
165 ChildNodesRef::InMemory(nodes) => Ok(nodes
165 ChildNodesRef::InMemory(nodes) => Ok(nodes
166 .get_key_value(base_name)
166 .get_key_value(base_name)
167 .map(|(k, v)| NodeRef::InMemory(k, v))),
167 .map(|(k, v)| NodeRef::InMemory(k, v))),
168 ChildNodesRef::OnDisk(nodes) => {
168 ChildNodesRef::OnDisk(nodes) => {
169 let mut parse_result = Ok(());
169 let mut parse_result = Ok(());
170 let search_result = nodes.binary_search_by(|node| {
170 let search_result = nodes.binary_search_by(|node| {
171 match node.base_name(on_disk) {
171 match node.base_name(on_disk) {
172 Ok(node_base_name) => node_base_name.cmp(base_name),
172 Ok(node_base_name) => node_base_name.cmp(base_name),
173 Err(e) => {
173 Err(e) => {
174 parse_result = Err(e);
174 parse_result = Err(e);
175 // Dummy comparison result, `search_result` won’t
175 // Dummy comparison result, `search_result` won’t
176 // be used since `parse_result` is an error
176 // be used since `parse_result` is an error
177 std::cmp::Ordering::Equal
177 std::cmp::Ordering::Equal
178 }
178 }
179 }
179 }
180 });
180 });
181 parse_result.map(|()| {
181 parse_result.map(|()| {
182 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
182 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
183 })
183 })
184 }
184 }
185 }
185 }
186 }
186 }
187
187
188 /// Iterate in undefined order
188 /// Iterate in undefined order
189 pub(super) fn iter(
189 pub(super) fn iter(
190 &self,
190 &self,
191 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
191 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
192 match self {
192 match self {
193 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
193 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
194 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
194 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
195 ),
195 ),
196 ChildNodesRef::OnDisk(nodes) => {
196 ChildNodesRef::OnDisk(nodes) => {
197 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
197 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
198 }
198 }
199 }
199 }
200 }
200 }
201
201
202 /// Iterate in parallel in undefined order
202 /// Iterate in parallel in undefined order
203 pub(super) fn par_iter(
203 pub(super) fn par_iter(
204 &self,
204 &self,
205 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
205 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
206 {
206 {
207 use rayon::prelude::*;
207 use rayon::prelude::*;
208 match self {
208 match self {
209 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
209 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
210 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
210 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
211 ),
211 ),
212 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
212 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
213 nodes.par_iter().map(NodeRef::OnDisk),
213 nodes.par_iter().map(NodeRef::OnDisk),
214 ),
214 ),
215 }
215 }
216 }
216 }
217
217
218 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
218 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
219 match self {
219 match self {
220 ChildNodesRef::InMemory(nodes) => {
220 ChildNodesRef::InMemory(nodes) => {
221 let mut vec: Vec<_> = nodes
221 let mut vec: Vec<_> = nodes
222 .iter()
222 .iter()
223 .map(|(k, v)| NodeRef::InMemory(k, v))
223 .map(|(k, v)| NodeRef::InMemory(k, v))
224 .collect();
224 .collect();
225 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
225 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
226 match node {
226 match node {
227 NodeRef::InMemory(path, _node) => path.base_name(),
227 NodeRef::InMemory(path, _node) => path.base_name(),
228 NodeRef::OnDisk(_) => unreachable!(),
228 NodeRef::OnDisk(_) => unreachable!(),
229 }
229 }
230 }
230 }
231 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
231 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
232 // value: https://github.com/rust-lang/rust/issues/34162
232 // value: https://github.com/rust-lang/rust/issues/34162
233 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
233 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
234 vec
234 vec
235 }
235 }
236 ChildNodesRef::OnDisk(nodes) => {
236 ChildNodesRef::OnDisk(nodes) => {
237 // Nodes on disk are already sorted
237 // Nodes on disk are already sorted
238 nodes.iter().map(NodeRef::OnDisk).collect()
238 nodes.iter().map(NodeRef::OnDisk).collect()
239 }
239 }
240 }
240 }
241 }
241 }
242 }
242 }
243
243
244 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
244 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
245 pub(super) fn full_path(
245 pub(super) fn full_path(
246 &self,
246 &self,
247 on_disk: &'on_disk [u8],
247 on_disk: &'on_disk [u8],
248 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
248 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
249 match self {
249 match self {
250 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
250 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
251 NodeRef::OnDisk(node) => node.full_path(on_disk),
251 NodeRef::OnDisk(node) => node.full_path(on_disk),
252 }
252 }
253 }
253 }
254
254
255 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
255 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
256 /// HgPath>` detached from `'tree`
256 /// HgPath>` detached from `'tree`
257 pub(super) fn full_path_borrowed(
257 pub(super) fn full_path_borrowed(
258 &self,
258 &self,
259 on_disk: &'on_disk [u8],
259 on_disk: &'on_disk [u8],
260 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
260 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
261 match self {
261 match self {
262 NodeRef::InMemory(path, _node) => match path.full_path() {
262 NodeRef::InMemory(path, _node) => match path.full_path() {
263 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
263 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
264 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
264 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
265 },
265 },
266 NodeRef::OnDisk(node) => {
266 NodeRef::OnDisk(node) => {
267 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
267 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
268 }
268 }
269 }
269 }
270 }
270 }
271
271
272 pub(super) fn base_name(
272 pub(super) fn base_name(
273 &self,
273 &self,
274 on_disk: &'on_disk [u8],
274 on_disk: &'on_disk [u8],
275 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
275 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
276 match self {
276 match self {
277 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
277 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
278 NodeRef::OnDisk(node) => node.base_name(on_disk),
278 NodeRef::OnDisk(node) => node.base_name(on_disk),
279 }
279 }
280 }
280 }
281
281
282 pub(super) fn children(
282 pub(super) fn children(
283 &self,
283 &self,
284 on_disk: &'on_disk [u8],
284 on_disk: &'on_disk [u8],
285 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
285 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
286 match self {
286 match self {
287 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
287 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
288 NodeRef::OnDisk(node) => {
288 NodeRef::OnDisk(node) => {
289 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
289 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
290 }
290 }
291 }
291 }
292 }
292 }
293
293
294 pub(super) fn has_copy_source(&self) -> bool {
294 pub(super) fn has_copy_source(&self) -> bool {
295 match self {
295 match self {
296 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
296 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
297 NodeRef::OnDisk(node) => node.has_copy_source(),
297 NodeRef::OnDisk(node) => node.has_copy_source(),
298 }
298 }
299 }
299 }
300
300
301 pub(super) fn copy_source(
301 pub(super) fn copy_source(
302 &self,
302 &self,
303 on_disk: &'on_disk [u8],
303 on_disk: &'on_disk [u8],
304 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
304 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
305 match self {
305 match self {
306 NodeRef::InMemory(_path, node) => {
306 NodeRef::InMemory(_path, node) => {
307 Ok(node.copy_source.as_ref().map(|s| &**s))
307 Ok(node.copy_source.as_ref().map(|s| &**s))
308 }
308 }
309 NodeRef::OnDisk(node) => node.copy_source(on_disk),
309 NodeRef::OnDisk(node) => node.copy_source(on_disk),
310 }
310 }
311 }
311 }
312
312
313 pub(super) fn entry(
313 pub(super) fn entry(
314 &self,
314 &self,
315 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
315 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
316 match self {
316 match self {
317 NodeRef::InMemory(_path, node) => {
317 NodeRef::InMemory(_path, node) => {
318 Ok(node.data.as_entry().copied())
318 Ok(node.data.as_entry().copied())
319 }
319 }
320 NodeRef::OnDisk(node) => node.entry(),
320 NodeRef::OnDisk(node) => node.entry(),
321 }
321 }
322 }
322 }
323
323
324 pub(super) fn state(
324 pub(super) fn state(
325 &self,
325 &self,
326 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
326 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
327 match self {
327 match self {
328 NodeRef::InMemory(_path, node) => {
328 NodeRef::InMemory(_path, node) => {
329 Ok(node.data.as_entry().map(|entry| entry.state()))
329 Ok(node.data.as_entry().map(|entry| entry.state()))
330 }
330 }
331 NodeRef::OnDisk(node) => node.state(),
331 NodeRef::OnDisk(node) => node.state(),
332 }
332 }
333 }
333 }
334
334
335 pub(super) fn cached_directory_mtime(
335 pub(super) fn cached_directory_mtime(
336 &self,
336 &self,
337 ) -> Option<&'tree on_disk::Timestamp> {
337 ) -> Option<&'tree on_disk::Timestamp> {
338 match self {
338 match self {
339 NodeRef::InMemory(_path, node) => match &node.data {
339 NodeRef::InMemory(_path, node) => match &node.data {
340 NodeData::CachedDirectory { mtime } => Some(mtime),
340 NodeData::CachedDirectory { mtime } => Some(mtime),
341 _ => None,
341 _ => None,
342 },
342 },
343 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
343 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
344 }
344 }
345 }
345 }
346
346
347 pub(super) fn descendants_with_entry_count(&self) -> u32 {
347 pub(super) fn descendants_with_entry_count(&self) -> u32 {
348 match self {
348 match self {
349 NodeRef::InMemory(_path, node) => {
349 NodeRef::InMemory(_path, node) => {
350 node.descendants_with_entry_count
350 node.descendants_with_entry_count
351 }
351 }
352 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
352 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
353 }
353 }
354 }
354 }
355
355
356 pub(super) fn tracked_descendants_count(&self) -> u32 {
356 pub(super) fn tracked_descendants_count(&self) -> u32 {
357 match self {
357 match self {
358 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
358 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
359 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
359 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
360 }
360 }
361 }
361 }
362 }
362 }
363
363
364 /// Represents a file or a directory
364 /// Represents a file or a directory
365 #[derive(Default)]
365 #[derive(Default)]
366 pub(super) struct Node<'on_disk> {
366 pub(super) struct Node<'on_disk> {
367 pub(super) data: NodeData,
367 pub(super) data: NodeData,
368
368
369 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
369 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
370
370
371 pub(super) children: ChildNodes<'on_disk>,
371 pub(super) children: ChildNodes<'on_disk>,
372
372
373 /// How many (non-inclusive) descendants of this node have an entry.
373 /// How many (non-inclusive) descendants of this node have an entry.
374 pub(super) descendants_with_entry_count: u32,
374 pub(super) descendants_with_entry_count: u32,
375
375
376 /// How many (non-inclusive) descendants of this node have an entry whose
376 /// How many (non-inclusive) descendants of this node have an entry whose
377 /// state is "tracked".
377 /// state is "tracked".
378 pub(super) tracked_descendants_count: u32,
378 pub(super) tracked_descendants_count: u32,
379 }
379 }
380
380
381 pub(super) enum NodeData {
381 pub(super) enum NodeData {
382 Entry(DirstateEntry),
382 Entry(DirstateEntry),
383 CachedDirectory { mtime: on_disk::Timestamp },
383 CachedDirectory { mtime: on_disk::Timestamp },
384 None,
384 None,
385 }
385 }
386
386
387 impl Default for NodeData {
387 impl Default for NodeData {
388 fn default() -> Self {
388 fn default() -> Self {
389 NodeData::None
389 NodeData::None
390 }
390 }
391 }
391 }
392
392
393 impl NodeData {
393 impl NodeData {
394 fn has_entry(&self) -> bool {
394 fn has_entry(&self) -> bool {
395 match self {
395 match self {
396 NodeData::Entry(_) => true,
396 NodeData::Entry(_) => true,
397 _ => false,
397 _ => false,
398 }
398 }
399 }
399 }
400
400
401 fn as_entry(&self) -> Option<&DirstateEntry> {
401 fn as_entry(&self) -> Option<&DirstateEntry> {
402 match self {
402 match self {
403 NodeData::Entry(entry) => Some(entry),
403 NodeData::Entry(entry) => Some(entry),
404 _ => None,
404 _ => None,
405 }
405 }
406 }
406 }
407 }
407 }
408
408
409 impl<'on_disk> DirstateMap<'on_disk> {
409 impl<'on_disk> DirstateMap<'on_disk> {
410 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
410 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
411 Self {
411 Self {
412 on_disk,
412 on_disk,
413 root: ChildNodes::default(),
413 root: ChildNodes::default(),
414 nodes_with_entry_count: 0,
414 nodes_with_entry_count: 0,
415 nodes_with_copy_source_count: 0,
415 nodes_with_copy_source_count: 0,
416 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
416 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
417 unreachable_bytes: 0,
417 unreachable_bytes: 0,
418 }
418 }
419 }
419 }
420
420
421 #[timed]
421 #[timed]
422 pub fn new_v2(
422 pub fn new_v2(
423 on_disk: &'on_disk [u8],
423 on_disk: &'on_disk [u8],
424 data_size: usize,
424 data_size: usize,
425 metadata: &[u8],
425 metadata: &[u8],
426 ) -> Result<Self, DirstateError> {
426 ) -> Result<Self, DirstateError> {
427 if let Some(data) = on_disk.get(..data_size) {
427 if let Some(data) = on_disk.get(..data_size) {
428 Ok(on_disk::read(data, metadata)?)
428 Ok(on_disk::read(data, metadata)?)
429 } else {
429 } else {
430 Err(DirstateV2ParseError.into())
430 Err(DirstateV2ParseError.into())
431 }
431 }
432 }
432 }
433
433
434 #[timed]
434 #[timed]
435 pub fn new_v1(
435 pub fn new_v1(
436 on_disk: &'on_disk [u8],
436 on_disk: &'on_disk [u8],
437 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
437 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
438 let mut map = Self::empty(on_disk);
438 let mut map = Self::empty(on_disk);
439 if map.on_disk.is_empty() {
439 if map.on_disk.is_empty() {
440 return Ok((map, None));
440 return Ok((map, None));
441 }
441 }
442
442
443 let parents = parse_dirstate_entries(
443 let parents = parse_dirstate_entries(
444 map.on_disk,
444 map.on_disk,
445 |path, entry, copy_source| {
445 |path, entry, copy_source| {
446 let tracked = entry.state().is_tracked();
446 let tracked = entry.state().is_tracked();
447 let node = Self::get_or_insert_node(
447 let node = Self::get_or_insert_node(
448 map.on_disk,
448 map.on_disk,
449 &mut map.unreachable_bytes,
449 &mut map.unreachable_bytes,
450 &mut map.root,
450 &mut map.root,
451 path,
451 path,
452 WithBasename::to_cow_borrowed,
452 WithBasename::to_cow_borrowed,
453 |ancestor| {
453 |ancestor| {
454 if tracked {
454 if tracked {
455 ancestor.tracked_descendants_count += 1
455 ancestor.tracked_descendants_count += 1
456 }
456 }
457 ancestor.descendants_with_entry_count += 1
457 ancestor.descendants_with_entry_count += 1
458 },
458 },
459 )?;
459 )?;
460 assert!(
460 assert!(
461 !node.data.has_entry(),
461 !node.data.has_entry(),
462 "duplicate dirstate entry in read"
462 "duplicate dirstate entry in read"
463 );
463 );
464 assert!(
464 assert!(
465 node.copy_source.is_none(),
465 node.copy_source.is_none(),
466 "duplicate dirstate entry in read"
466 "duplicate dirstate entry in read"
467 );
467 );
468 node.data = NodeData::Entry(*entry);
468 node.data = NodeData::Entry(*entry);
469 node.copy_source = copy_source.map(Cow::Borrowed);
469 node.copy_source = copy_source.map(Cow::Borrowed);
470 map.nodes_with_entry_count += 1;
470 map.nodes_with_entry_count += 1;
471 if copy_source.is_some() {
471 if copy_source.is_some() {
472 map.nodes_with_copy_source_count += 1
472 map.nodes_with_copy_source_count += 1
473 }
473 }
474 Ok(())
474 Ok(())
475 },
475 },
476 )?;
476 )?;
477 let parents = Some(parents.clone());
477 let parents = Some(parents.clone());
478
478
479 Ok((map, parents))
479 Ok((map, parents))
480 }
480 }
481
481
482 /// Assuming dirstate-v2 format, returns whether the next write should
482 /// Assuming dirstate-v2 format, returns whether the next write should
483 /// append to the existing data file that contains `self.on_disk` (true),
483 /// append to the existing data file that contains `self.on_disk` (true),
484 /// or create a new data file from scratch (false).
484 /// or create a new data file from scratch (false).
485 pub(super) fn write_should_append(&self) -> bool {
485 pub(super) fn write_should_append(&self) -> bool {
486 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
486 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
487 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
487 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
488 }
488 }
489
489
490 fn get_node<'tree>(
490 fn get_node<'tree>(
491 &'tree self,
491 &'tree self,
492 path: &HgPath,
492 path: &HgPath,
493 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
493 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
494 let mut children = self.root.as_ref();
494 let mut children = self.root.as_ref();
495 let mut components = path.components();
495 let mut components = path.components();
496 let mut component =
496 let mut component =
497 components.next().expect("expected at least one components");
497 components.next().expect("expected at least one components");
498 loop {
498 loop {
499 if let Some(child) = children.get(component, self.on_disk)? {
499 if let Some(child) = children.get(component, self.on_disk)? {
500 if let Some(next_component) = components.next() {
500 if let Some(next_component) = components.next() {
501 component = next_component;
501 component = next_component;
502 children = child.children(self.on_disk)?;
502 children = child.children(self.on_disk)?;
503 } else {
503 } else {
504 return Ok(Some(child));
504 return Ok(Some(child));
505 }
505 }
506 } else {
506 } else {
507 return Ok(None);
507 return Ok(None);
508 }
508 }
509 }
509 }
510 }
510 }
511
511
512 /// Returns a mutable reference to the node at `path` if it exists
512 /// Returns a mutable reference to the node at `path` if it exists
513 ///
513 ///
514 /// This takes `root` instead of `&mut self` so that callers can mutate
514 /// This takes `root` instead of `&mut self` so that callers can mutate
515 /// other fields while the returned borrow is still valid
515 /// other fields while the returned borrow is still valid
516 fn get_node_mut<'tree>(
516 fn get_node_mut<'tree>(
517 on_disk: &'on_disk [u8],
517 on_disk: &'on_disk [u8],
518 unreachable_bytes: &mut u32,
518 unreachable_bytes: &mut u32,
519 root: &'tree mut ChildNodes<'on_disk>,
519 root: &'tree mut ChildNodes<'on_disk>,
520 path: &HgPath,
520 path: &HgPath,
521 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
521 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
522 let mut children = root;
522 let mut children = root;
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
527 if let Some(child) = children
528 .make_mut(on_disk, unreachable_bytes)?
528 .make_mut(on_disk, unreachable_bytes)?
529 .get_mut(component)
529 .get_mut(component)
530 {
530 {
531 if let Some(next_component) = components.next() {
531 if let Some(next_component) = components.next() {
532 component = next_component;
532 component = next_component;
533 children = &mut child.children;
533 children = &mut child.children;
534 } else {
534 } else {
535 return Ok(Some(child));
535 return Ok(Some(child));
536 }
536 }
537 } else {
537 } else {
538 return Ok(None);
538 return Ok(None);
539 }
539 }
540 }
540 }
541 }
541 }
542
542
543 pub(super) fn get_or_insert<'tree, 'path>(
543 pub(super) fn get_or_insert<'tree, 'path>(
544 &'tree mut self,
544 &'tree mut self,
545 path: &HgPath,
545 path: &HgPath,
546 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
546 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
547 Self::get_or_insert_node(
547 Self::get_or_insert_node(
548 self.on_disk,
548 self.on_disk,
549 &mut self.unreachable_bytes,
549 &mut self.unreachable_bytes,
550 &mut self.root,
550 &mut self.root,
551 path,
551 path,
552 WithBasename::to_cow_owned,
552 WithBasename::to_cow_owned,
553 |_| {},
553 |_| {},
554 )
554 )
555 }
555 }
556
556
557 fn get_or_insert_node<'tree, 'path>(
557 fn get_or_insert_node<'tree, 'path>(
558 on_disk: &'on_disk [u8],
558 on_disk: &'on_disk [u8],
559 unreachable_bytes: &mut u32,
559 unreachable_bytes: &mut u32,
560 root: &'tree mut ChildNodes<'on_disk>,
560 root: &'tree mut ChildNodes<'on_disk>,
561 path: &'path HgPath,
561 path: &'path HgPath,
562 to_cow: impl Fn(
562 to_cow: impl Fn(
563 WithBasename<&'path HgPath>,
563 WithBasename<&'path HgPath>,
564 ) -> WithBasename<Cow<'on_disk, HgPath>>,
564 ) -> WithBasename<Cow<'on_disk, HgPath>>,
565 mut each_ancestor: impl FnMut(&mut Node),
565 mut each_ancestor: impl FnMut(&mut Node),
566 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
566 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
567 let mut child_nodes = root;
567 let mut child_nodes = root;
568 let mut inclusive_ancestor_paths =
568 let mut inclusive_ancestor_paths =
569 WithBasename::inclusive_ancestors_of(path);
569 WithBasename::inclusive_ancestors_of(path);
570 let mut ancestor_path = inclusive_ancestor_paths
570 let mut ancestor_path = inclusive_ancestor_paths
571 .next()
571 .next()
572 .expect("expected at least one inclusive ancestor");
572 .expect("expected at least one inclusive ancestor");
573 loop {
573 loop {
574 // TODO: can we avoid allocating an owned key in cases where the
574 // TODO: can we avoid allocating an owned key in cases where the
575 // map already contains that key, without introducing double
575 // map already contains that key, without introducing double
576 // lookup?
576 // lookup?
577 let child_node = child_nodes
577 let child_node = child_nodes
578 .make_mut(on_disk, unreachable_bytes)?
578 .make_mut(on_disk, unreachable_bytes)?
579 .entry(to_cow(ancestor_path))
579 .entry(to_cow(ancestor_path))
580 .or_default();
580 .or_default();
581 if let Some(next) = inclusive_ancestor_paths.next() {
581 if let Some(next) = inclusive_ancestor_paths.next() {
582 each_ancestor(child_node);
582 each_ancestor(child_node);
583 ancestor_path = next;
583 ancestor_path = next;
584 child_nodes = &mut child_node.children;
584 child_nodes = &mut child_node.children;
585 } else {
585 } else {
586 return Ok(child_node);
586 return Ok(child_node);
587 }
587 }
588 }
588 }
589 }
589 }
590
590
591 fn add_or_remove_file(
591 fn add_or_remove_file(
592 &mut self,
592 &mut self,
593 path: &HgPath,
593 path: &HgPath,
594 old_state: Option<EntryState>,
594 old_state: Option<EntryState>,
595 new_entry: DirstateEntry,
595 new_entry: DirstateEntry,
596 ) -> Result<(), DirstateV2ParseError> {
596 ) -> Result<(), DirstateV2ParseError> {
597 let had_entry = old_state.is_some();
597 let had_entry = old_state.is_some();
598 let was_tracked = old_state.map_or(false, |s| s.is_tracked());
598 let was_tracked = old_state.map_or(false, |s| s.is_tracked());
599 let tracked_count_increment =
599 let tracked_count_increment =
600 match (was_tracked, new_entry.state().is_tracked()) {
600 match (was_tracked, new_entry.state().is_tracked()) {
601 (false, true) => 1,
601 (false, true) => 1,
602 (true, false) => -1,
602 (true, false) => -1,
603 _ => 0,
603 _ => 0,
604 };
604 };
605
605
606 let node = Self::get_or_insert_node(
606 let node = Self::get_or_insert_node(
607 self.on_disk,
607 self.on_disk,
608 &mut self.unreachable_bytes,
608 &mut self.unreachable_bytes,
609 &mut self.root,
609 &mut self.root,
610 path,
610 path,
611 WithBasename::to_cow_owned,
611 WithBasename::to_cow_owned,
612 |ancestor| {
612 |ancestor| {
613 if !had_entry {
613 if !had_entry {
614 ancestor.descendants_with_entry_count += 1;
614 ancestor.descendants_with_entry_count += 1;
615 }
615 }
616
616
617 // We can’t use `+= increment` because the counter is unsigned,
617 // We can’t use `+= increment` because the counter is unsigned,
618 // and we want debug builds to detect accidental underflow
618 // and we want debug builds to detect accidental underflow
619 // through zero
619 // through zero
620 match tracked_count_increment {
620 match tracked_count_increment {
621 1 => ancestor.tracked_descendants_count += 1,
621 1 => ancestor.tracked_descendants_count += 1,
622 -1 => ancestor.tracked_descendants_count -= 1,
622 -1 => ancestor.tracked_descendants_count -= 1,
623 _ => {}
623 _ => {}
624 }
624 }
625 },
625 },
626 )?;
626 )?;
627 if !had_entry {
627 if !had_entry {
628 self.nodes_with_entry_count += 1
628 self.nodes_with_entry_count += 1
629 }
629 }
630 node.data = NodeData::Entry(new_entry);
630 node.data = NodeData::Entry(new_entry);
631 Ok(())
631 Ok(())
632 }
632 }
633
633
634 fn iter_nodes<'tree>(
634 fn iter_nodes<'tree>(
635 &'tree self,
635 &'tree self,
636 ) -> impl Iterator<
636 ) -> impl Iterator<
637 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
637 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
638 > + 'tree {
638 > + 'tree {
639 // Depth first tree traversal.
639 // Depth first tree traversal.
640 //
640 //
641 // If we could afford internal iteration and recursion,
641 // If we could afford internal iteration and recursion,
642 // this would look like:
642 // this would look like:
643 //
643 //
644 // ```
644 // ```
645 // fn traverse_children(
645 // fn traverse_children(
646 // children: &ChildNodes,
646 // children: &ChildNodes,
647 // each: &mut impl FnMut(&Node),
647 // each: &mut impl FnMut(&Node),
648 // ) {
648 // ) {
649 // for child in children.values() {
649 // for child in children.values() {
650 // traverse_children(&child.children, each);
650 // traverse_children(&child.children, each);
651 // each(child);
651 // each(child);
652 // }
652 // }
653 // }
653 // }
654 // ```
654 // ```
655 //
655 //
656 // However we want an external iterator and therefore can’t use the
656 // However we want an external iterator and therefore can’t use the
657 // call stack. Use an explicit stack instead:
657 // call stack. Use an explicit stack instead:
658 let mut stack = Vec::new();
658 let mut stack = Vec::new();
659 let mut iter = self.root.as_ref().iter();
659 let mut iter = self.root.as_ref().iter();
660 std::iter::from_fn(move || {
660 std::iter::from_fn(move || {
661 while let Some(child_node) = iter.next() {
661 while let Some(child_node) = iter.next() {
662 let children = match child_node.children(self.on_disk) {
662 let children = match child_node.children(self.on_disk) {
663 Ok(children) => children,
663 Ok(children) => children,
664 Err(error) => return Some(Err(error)),
664 Err(error) => return Some(Err(error)),
665 };
665 };
666 // Pseudo-recursion
666 // Pseudo-recursion
667 let new_iter = children.iter();
667 let new_iter = children.iter();
668 let old_iter = std::mem::replace(&mut iter, new_iter);
668 let old_iter = std::mem::replace(&mut iter, new_iter);
669 stack.push((child_node, old_iter));
669 stack.push((child_node, old_iter));
670 }
670 }
671 // Found the end of a `children.iter()` iterator.
671 // Found the end of a `children.iter()` iterator.
672 if let Some((child_node, next_iter)) = stack.pop() {
672 if let Some((child_node, next_iter)) = stack.pop() {
673 // "Return" from pseudo-recursion by restoring state from the
673 // "Return" from pseudo-recursion by restoring state from the
674 // explicit stack
674 // explicit stack
675 iter = next_iter;
675 iter = next_iter;
676
676
677 Some(Ok(child_node))
677 Some(Ok(child_node))
678 } else {
678 } else {
679 // Reached the bottom of the stack, we’re done
679 // Reached the bottom of the stack, we’re done
680 None
680 None
681 }
681 }
682 })
682 })
683 }
683 }
684
684
685 fn clear_known_ambiguous_mtimes(
685 fn clear_known_ambiguous_mtimes(
686 &mut self,
686 &mut self,
687 paths: &[impl AsRef<HgPath>],
687 paths: &[impl AsRef<HgPath>],
688 ) -> Result<(), DirstateV2ParseError> {
688 ) -> Result<(), DirstateV2ParseError> {
689 for path in paths {
689 for path in paths {
690 if let Some(node) = Self::get_node_mut(
690 if let Some(node) = Self::get_node_mut(
691 self.on_disk,
691 self.on_disk,
692 &mut self.unreachable_bytes,
692 &mut self.unreachable_bytes,
693 &mut self.root,
693 &mut self.root,
694 path.as_ref(),
694 path.as_ref(),
695 )? {
695 )? {
696 if let NodeData::Entry(entry) = &mut node.data {
696 if let NodeData::Entry(entry) = &mut node.data {
697 entry.clear_mtime();
697 entry.clear_mtime();
698 }
698 }
699 }
699 }
700 }
700 }
701 Ok(())
701 Ok(())
702 }
702 }
703
703
704 /// Return a faillilble iterator of full paths of nodes that have an
704 /// Return a faillilble iterator of full paths of nodes that have an
705 /// `entry` for which the given `predicate` returns true.
705 /// `entry` for which the given `predicate` returns true.
706 ///
706 ///
707 /// Fallibility means that each iterator item is a `Result`, which may
707 /// Fallibility means that each iterator item is a `Result`, which may
708 /// indicate a parse error of the on-disk dirstate-v2 format. Such errors
708 /// indicate a parse error of the on-disk dirstate-v2 format. Such errors
709 /// should only happen if Mercurial is buggy or a repository is corrupted.
709 /// should only happen if Mercurial is buggy or a repository is corrupted.
710 fn filter_full_paths<'tree>(
710 fn filter_full_paths<'tree>(
711 &'tree self,
711 &'tree self,
712 predicate: impl Fn(&DirstateEntry) -> bool + 'tree,
712 predicate: impl Fn(&DirstateEntry) -> bool + 'tree,
713 ) -> impl Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + 'tree
713 ) -> impl Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + 'tree
714 {
714 {
715 filter_map_results(self.iter_nodes(), move |node| {
715 filter_map_results(self.iter_nodes(), move |node| {
716 if let Some(entry) = node.entry()? {
716 if let Some(entry) = node.entry()? {
717 if predicate(&entry) {
717 if predicate(&entry) {
718 return Ok(Some(node.full_path(self.on_disk)?));
718 return Ok(Some(node.full_path(self.on_disk)?));
719 }
719 }
720 }
720 }
721 Ok(None)
721 Ok(None)
722 })
722 })
723 }
723 }
724
724
725 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
725 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
726 if let Cow::Borrowed(path) = path {
726 if let Cow::Borrowed(path) = path {
727 *unreachable_bytes += path.len() as u32
727 *unreachable_bytes += path.len() as u32
728 }
728 }
729 }
729 }
730 }
730 }
731
731
732 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
732 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
733 ///
733 ///
734 /// The callback is only called for incoming `Ok` values. Errors are passed
734 /// The callback is only called for incoming `Ok` values. Errors are passed
735 /// through as-is. In order to let it use the `?` operator the callback is
735 /// through as-is. In order to let it use the `?` operator the callback is
736 /// expected to return a `Result` of `Option`, instead of an `Option` of
736 /// expected to return a `Result` of `Option`, instead of an `Option` of
737 /// `Result`.
737 /// `Result`.
738 fn filter_map_results<'a, I, F, A, B, E>(
738 fn filter_map_results<'a, I, F, A, B, E>(
739 iter: I,
739 iter: I,
740 f: F,
740 f: F,
741 ) -> impl Iterator<Item = Result<B, E>> + 'a
741 ) -> impl Iterator<Item = Result<B, E>> + 'a
742 where
742 where
743 I: Iterator<Item = Result<A, E>> + 'a,
743 I: Iterator<Item = Result<A, E>> + 'a,
744 F: Fn(A) -> Result<Option<B>, E> + 'a,
744 F: Fn(A) -> Result<Option<B>, E> + 'a,
745 {
745 {
746 iter.filter_map(move |result| match result {
746 iter.filter_map(move |result| match result {
747 Ok(node) => f(node).transpose(),
747 Ok(node) => f(node).transpose(),
748 Err(e) => Some(Err(e)),
748 Err(e) => Some(Err(e)),
749 })
749 })
750 }
750 }
751
751
752 impl<'on_disk> super::dispatch::DirstateMapMethods for DirstateMap<'on_disk> {
752 impl<'on_disk> super::dispatch::DirstateMapMethods for DirstateMap<'on_disk> {
753 fn clear(&mut self) {
753 fn clear(&mut self) {
754 self.root = Default::default();
754 self.root = Default::default();
755 self.nodes_with_entry_count = 0;
755 self.nodes_with_entry_count = 0;
756 self.nodes_with_copy_source_count = 0;
756 self.nodes_with_copy_source_count = 0;
757 }
757 }
758
758
759 fn set_entry(
759 fn set_entry(
760 &mut self,
760 &mut self,
761 filename: &HgPath,
761 filename: &HgPath,
762 entry: DirstateEntry,
762 entry: DirstateEntry,
763 ) -> Result<(), DirstateV2ParseError> {
763 ) -> Result<(), DirstateV2ParseError> {
764 self.get_or_insert(&filename)?.data = NodeData::Entry(entry);
764 self.get_or_insert(&filename)?.data = NodeData::Entry(entry);
765 Ok(())
765 Ok(())
766 }
766 }
767
767
768 fn add_file(
768 fn add_file(
769 &mut self,
769 &mut self,
770 filename: &HgPath,
770 filename: &HgPath,
771 entry: DirstateEntry,
771 entry: DirstateEntry,
772 ) -> Result<(), DirstateError> {
772 ) -> Result<(), DirstateError> {
773 let old_state = self.get(filename)?.map(|e| e.state());
773 let old_state = self.get(filename)?.map(|e| e.state());
774 Ok(self.add_or_remove_file(filename, old_state, entry)?)
774 Ok(self.add_or_remove_file(filename, old_state, entry)?)
775 }
775 }
776
776
777 fn remove_file(
777 fn remove_file(
778 &mut self,
778 &mut self,
779 filename: &HgPath,
779 filename: &HgPath,
780 in_merge: bool,
780 in_merge: bool,
781 ) -> Result<(), DirstateError> {
781 ) -> Result<(), DirstateError> {
782 let old_entry_opt = self.get(filename)?;
782 let old_entry_opt = self.get(filename)?;
783 let old_state = old_entry_opt.map(|e| e.state());
783 let old_state = old_entry_opt.map(|e| e.state());
784 let mut size = 0;
784 let mut size = 0;
785 if in_merge {
785 if in_merge {
786 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
786 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
787 // during a merge. So I (marmoute) am not sure we need the
787 // during a merge. So I (marmoute) am not sure we need the
788 // conditionnal at all. Adding double checking this with assert
788 // conditionnal at all. Adding double checking this with assert
789 // would be nice.
789 // would be nice.
790 if let Some(old_entry) = old_entry_opt {
790 if let Some(old_entry) = old_entry_opt {
791 // backup the previous state
791 // backup the previous state
792 if old_entry.state() == EntryState::Merged {
792 if old_entry.state() == EntryState::Merged {
793 size = SIZE_NON_NORMAL;
793 size = SIZE_NON_NORMAL;
794 } else if old_entry.state() == EntryState::Normal
794 } else if old_entry.state() == EntryState::Normal
795 && old_entry.size() == SIZE_FROM_OTHER_PARENT
795 && old_entry.size() == SIZE_FROM_OTHER_PARENT
796 {
796 {
797 // other parent
797 // other parent
798 size = SIZE_FROM_OTHER_PARENT;
798 size = SIZE_FROM_OTHER_PARENT;
799 }
799 }
800 }
800 }
801 }
801 }
802 if size == 0 {
802 if size == 0 {
803 self.copy_map_remove(filename)?;
803 self.copy_map_remove(filename)?;
804 }
804 }
805 let entry = DirstateEntry::new_removed(size);
805 let entry = DirstateEntry::new_removed(size);
806 Ok(self.add_or_remove_file(filename, old_state, entry)?)
806 Ok(self.add_or_remove_file(filename, old_state, entry)?)
807 }
807 }
808
808
809 fn drop_entry_and_copy_source(
809 fn drop_entry_and_copy_source(
810 &mut self,
810 &mut self,
811 filename: &HgPath,
811 filename: &HgPath,
812 ) -> Result<(), DirstateError> {
812 ) -> Result<(), DirstateError> {
813 let was_tracked = self
813 let was_tracked = self
814 .get(filename)?
814 .get(filename)?
815 .map_or(false, |e| e.state().is_tracked());
815 .map_or(false, |e| e.state().is_tracked());
816 struct Dropped {
816 struct Dropped {
817 was_tracked: bool,
817 was_tracked: bool,
818 had_entry: bool,
818 had_entry: bool,
819 had_copy_source: bool,
819 had_copy_source: bool,
820 }
820 }
821
821
822 /// If this returns `Ok(Some((dropped, removed)))`, then
822 /// If this returns `Ok(Some((dropped, removed)))`, then
823 ///
823 ///
824 /// * `dropped` is about the leaf node that was at `filename`
824 /// * `dropped` is about the leaf node that was at `filename`
825 /// * `removed` is whether this particular level of recursion just
825 /// * `removed` is whether this particular level of recursion just
826 /// removed a node in `nodes`.
826 /// removed a node in `nodes`.
827 fn recur<'on_disk>(
827 fn recur<'on_disk>(
828 on_disk: &'on_disk [u8],
828 on_disk: &'on_disk [u8],
829 unreachable_bytes: &mut u32,
829 unreachable_bytes: &mut u32,
830 nodes: &mut ChildNodes<'on_disk>,
830 nodes: &mut ChildNodes<'on_disk>,
831 path: &HgPath,
831 path: &HgPath,
832 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
832 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
833 let (first_path_component, rest_of_path) =
833 let (first_path_component, rest_of_path) =
834 path.split_first_component();
834 path.split_first_component();
835 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
835 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
836 let node = if let Some(node) = nodes.get_mut(first_path_component)
836 let node = if let Some(node) = nodes.get_mut(first_path_component)
837 {
837 {
838 node
838 node
839 } else {
839 } else {
840 return Ok(None);
840 return Ok(None);
841 };
841 };
842 let dropped;
842 let dropped;
843 if let Some(rest) = rest_of_path {
843 if let Some(rest) = rest_of_path {
844 if let Some((d, removed)) = recur(
844 if let Some((d, removed)) = recur(
845 on_disk,
845 on_disk,
846 unreachable_bytes,
846 unreachable_bytes,
847 &mut node.children,
847 &mut node.children,
848 rest,
848 rest,
849 )? {
849 )? {
850 dropped = d;
850 dropped = d;
851 if dropped.had_entry {
851 if dropped.had_entry {
852 node.descendants_with_entry_count -= 1;
852 node.descendants_with_entry_count -= 1;
853 }
853 }
854 if dropped.was_tracked {
854 if dropped.was_tracked {
855 node.tracked_descendants_count -= 1;
855 node.tracked_descendants_count -= 1;
856 }
856 }
857
857
858 // Directory caches must be invalidated when removing a
858 // Directory caches must be invalidated when removing a
859 // child node
859 // child node
860 if removed {
860 if removed {
861 if let NodeData::CachedDirectory { .. } = &node.data {
861 if let NodeData::CachedDirectory { .. } = &node.data {
862 node.data = NodeData::None
862 node.data = NodeData::None
863 }
863 }
864 }
864 }
865 } else {
865 } else {
866 return Ok(None);
866 return Ok(None);
867 }
867 }
868 } else {
868 } else {
869 let had_entry = node.data.has_entry();
869 let had_entry = node.data.has_entry();
870 if had_entry {
870 if had_entry {
871 node.data = NodeData::None
871 node.data = NodeData::None
872 }
872 }
873 if let Some(source) = &node.copy_source {
873 if let Some(source) = &node.copy_source {
874 DirstateMap::count_dropped_path(unreachable_bytes, source);
874 DirstateMap::count_dropped_path(unreachable_bytes, source);
875 node.copy_source = None
875 node.copy_source = None
876 }
876 }
877 dropped = Dropped {
877 dropped = Dropped {
878 was_tracked: node
878 was_tracked: node
879 .data
879 .data
880 .as_entry()
880 .as_entry()
881 .map_or(false, |entry| entry.state().is_tracked()),
881 .map_or(false, |entry| entry.state().is_tracked()),
882 had_entry,
882 had_entry,
883 had_copy_source: node.copy_source.take().is_some(),
883 had_copy_source: node.copy_source.take().is_some(),
884 };
884 };
885 }
885 }
886 // After recursion, for both leaf (rest_of_path is None) nodes and
886 // After recursion, for both leaf (rest_of_path is None) nodes and
887 // parent nodes, remove a node if it just became empty.
887 // parent nodes, remove a node if it just became empty.
888 let remove = !node.data.has_entry()
888 let remove = !node.data.has_entry()
889 && node.copy_source.is_none()
889 && node.copy_source.is_none()
890 && node.children.is_empty();
890 && node.children.is_empty();
891 if remove {
891 if remove {
892 let (key, _) =
892 let (key, _) =
893 nodes.remove_entry(first_path_component).unwrap();
893 nodes.remove_entry(first_path_component).unwrap();
894 DirstateMap::count_dropped_path(
894 DirstateMap::count_dropped_path(
895 unreachable_bytes,
895 unreachable_bytes,
896 key.full_path(),
896 key.full_path(),
897 )
897 )
898 }
898 }
899 Ok(Some((dropped, remove)))
899 Ok(Some((dropped, remove)))
900 }
900 }
901
901
902 if let Some((dropped, _removed)) = recur(
902 if let Some((dropped, _removed)) = recur(
903 self.on_disk,
903 self.on_disk,
904 &mut self.unreachable_bytes,
904 &mut self.unreachable_bytes,
905 &mut self.root,
905 &mut self.root,
906 filename,
906 filename,
907 )? {
907 )? {
908 if dropped.had_entry {
908 if dropped.had_entry {
909 self.nodes_with_entry_count -= 1
909 self.nodes_with_entry_count -= 1
910 }
910 }
911 if dropped.had_copy_source {
911 if dropped.had_copy_source {
912 self.nodes_with_copy_source_count -= 1
912 self.nodes_with_copy_source_count -= 1
913 }
913 }
914 } else {
914 } else {
915 debug_assert!(!was_tracked);
915 debug_assert!(!was_tracked);
916 }
916 }
917 Ok(())
917 Ok(())
918 }
918 }
919
919
920 fn clear_ambiguous_times(
921 &mut self,
922 filenames: Vec<HgPathBuf>,
923 now: i32,
924 ) -> Result<(), DirstateV2ParseError> {
925 for filename in filenames {
926 if let Some(node) = Self::get_node_mut(
927 self.on_disk,
928 &mut self.unreachable_bytes,
929 &mut self.root,
930 &filename,
931 )? {
932 if let NodeData::Entry(entry) = &mut node.data {
933 entry.clear_ambiguous_mtime(now);
934 }
935 }
936 }
937 Ok(())
938 }
939
940 fn non_normal_entries_contains(
920 fn non_normal_entries_contains(
941 &mut self,
921 &mut self,
942 key: &HgPath,
922 key: &HgPath,
943 ) -> Result<bool, DirstateV2ParseError> {
923 ) -> Result<bool, DirstateV2ParseError> {
944 Ok(if let Some(node) = self.get_node(key)? {
924 Ok(if let Some(node) = self.get_node(key)? {
945 node.entry()?.map_or(false, |entry| entry.is_non_normal())
925 node.entry()?.map_or(false, |entry| entry.is_non_normal())
946 } else {
926 } else {
947 false
927 false
948 })
928 })
949 }
929 }
950
930
951 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool {
931 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool {
952 // Do nothing, this `DirstateMap` does not have a separate "non normal
932 // Do nothing, this `DirstateMap` does not have a separate "non normal
953 // entries" set that need to be kept up to date.
933 // entries" set that need to be kept up to date.
954 if let Ok(Some(v)) = self.get(key) {
934 if let Ok(Some(v)) = self.get(key) {
955 return v.is_non_normal();
935 return v.is_non_normal();
956 }
936 }
957 false
937 false
958 }
938 }
959
939
960 fn non_normal_entries_add(&mut self, _key: &HgPath) {
940 fn non_normal_entries_add(&mut self, _key: &HgPath) {
961 // Do nothing, this `DirstateMap` does not have a separate "non normal
941 // Do nothing, this `DirstateMap` does not have a separate "non normal
962 // entries" set that need to be kept up to date
942 // entries" set that need to be kept up to date
963 }
943 }
964
944
965 fn non_normal_or_other_parent_paths(
945 fn non_normal_or_other_parent_paths(
966 &mut self,
946 &mut self,
967 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
947 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
968 {
948 {
969 Box::new(self.filter_full_paths(|entry| {
949 Box::new(self.filter_full_paths(|entry| {
970 entry.is_non_normal() || entry.is_from_other_parent()
950 entry.is_non_normal() || entry.is_from_other_parent()
971 }))
951 }))
972 }
952 }
973
953
974 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
954 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
975 // Do nothing, this `DirstateMap` does not have a separate "non normal
955 // Do nothing, this `DirstateMap` does not have a separate "non normal
976 // entries" and "from other parent" sets that need to be recomputed
956 // entries" and "from other parent" sets that need to be recomputed
977 }
957 }
978
958
979 fn iter_non_normal_paths(
959 fn iter_non_normal_paths(
980 &mut self,
960 &mut self,
981 ) -> Box<
961 ) -> Box<
982 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
962 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
983 > {
963 > {
984 self.iter_non_normal_paths_panic()
964 self.iter_non_normal_paths_panic()
985 }
965 }
986
966
987 fn iter_non_normal_paths_panic(
967 fn iter_non_normal_paths_panic(
988 &self,
968 &self,
989 ) -> Box<
969 ) -> Box<
990 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
970 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
991 > {
971 > {
992 Box::new(self.filter_full_paths(|entry| entry.is_non_normal()))
972 Box::new(self.filter_full_paths(|entry| entry.is_non_normal()))
993 }
973 }
994
974
995 fn iter_other_parent_paths(
975 fn iter_other_parent_paths(
996 &mut self,
976 &mut self,
997 ) -> Box<
977 ) -> Box<
998 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
978 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
999 > {
979 > {
1000 Box::new(self.filter_full_paths(|entry| entry.is_from_other_parent()))
980 Box::new(self.filter_full_paths(|entry| entry.is_from_other_parent()))
1001 }
981 }
1002
982
1003 fn has_tracked_dir(
983 fn has_tracked_dir(
1004 &mut self,
984 &mut self,
1005 directory: &HgPath,
985 directory: &HgPath,
1006 ) -> Result<bool, DirstateError> {
986 ) -> Result<bool, DirstateError> {
1007 if let Some(node) = self.get_node(directory)? {
987 if let Some(node) = self.get_node(directory)? {
1008 // A node without a `DirstateEntry` was created to hold child
988 // A node without a `DirstateEntry` was created to hold child
1009 // nodes, and is therefore a directory.
989 // nodes, and is therefore a directory.
1010 let state = node.state()?;
990 let state = node.state()?;
1011 Ok(state.is_none() && node.tracked_descendants_count() > 0)
991 Ok(state.is_none() && node.tracked_descendants_count() > 0)
1012 } else {
992 } else {
1013 Ok(false)
993 Ok(false)
1014 }
994 }
1015 }
995 }
1016
996
1017 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
997 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
1018 if let Some(node) = self.get_node(directory)? {
998 if let Some(node) = self.get_node(directory)? {
1019 // A node without a `DirstateEntry` was created to hold child
999 // A node without a `DirstateEntry` was created to hold child
1020 // nodes, and is therefore a directory.
1000 // nodes, and is therefore a directory.
1021 let state = node.state()?;
1001 let state = node.state()?;
1022 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
1002 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
1023 } else {
1003 } else {
1024 Ok(false)
1004 Ok(false)
1025 }
1005 }
1026 }
1006 }
1027
1007
1028 #[timed]
1008 #[timed]
1029 fn pack_v1(
1009 fn pack_v1(
1030 &mut self,
1010 &mut self,
1031 parents: DirstateParents,
1011 parents: DirstateParents,
1032 now: Timestamp,
1012 now: Timestamp,
1033 ) -> Result<Vec<u8>, DirstateError> {
1013 ) -> Result<Vec<u8>, DirstateError> {
1034 let now: i32 = now.0.try_into().expect("time overflow");
1014 let now: i32 = now.0.try_into().expect("time overflow");
1035 let mut ambiguous_mtimes = Vec::new();
1015 let mut ambiguous_mtimes = Vec::new();
1036 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1016 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1037 // reallocations
1017 // reallocations
1038 let mut size = parents.as_bytes().len();
1018 let mut size = parents.as_bytes().len();
1039 for node in self.iter_nodes() {
1019 for node in self.iter_nodes() {
1040 let node = node?;
1020 let node = node?;
1041 if let Some(entry) = node.entry()? {
1021 if let Some(entry) = node.entry()? {
1042 size += packed_entry_size(
1022 size += packed_entry_size(
1043 node.full_path(self.on_disk)?,
1023 node.full_path(self.on_disk)?,
1044 node.copy_source(self.on_disk)?,
1024 node.copy_source(self.on_disk)?,
1045 );
1025 );
1046 if entry.mtime_is_ambiguous(now) {
1026 if entry.mtime_is_ambiguous(now) {
1047 ambiguous_mtimes.push(
1027 ambiguous_mtimes.push(
1048 node.full_path_borrowed(self.on_disk)?
1028 node.full_path_borrowed(self.on_disk)?
1049 .detach_from_tree(),
1029 .detach_from_tree(),
1050 )
1030 )
1051 }
1031 }
1052 }
1032 }
1053 }
1033 }
1054 self.clear_known_ambiguous_mtimes(&ambiguous_mtimes)?;
1034 self.clear_known_ambiguous_mtimes(&ambiguous_mtimes)?;
1055
1035
1056 let mut packed = Vec::with_capacity(size);
1036 let mut packed = Vec::with_capacity(size);
1057 packed.extend(parents.as_bytes());
1037 packed.extend(parents.as_bytes());
1058
1038
1059 for node in self.iter_nodes() {
1039 for node in self.iter_nodes() {
1060 let node = node?;
1040 let node = node?;
1061 if let Some(entry) = node.entry()? {
1041 if let Some(entry) = node.entry()? {
1062 pack_entry(
1042 pack_entry(
1063 node.full_path(self.on_disk)?,
1043 node.full_path(self.on_disk)?,
1064 &entry,
1044 &entry,
1065 node.copy_source(self.on_disk)?,
1045 node.copy_source(self.on_disk)?,
1066 &mut packed,
1046 &mut packed,
1067 );
1047 );
1068 }
1048 }
1069 }
1049 }
1070 Ok(packed)
1050 Ok(packed)
1071 }
1051 }
1072
1052
1073 /// Returns new data and metadata together with whether that data should be
1053 /// Returns new data and metadata together with whether that data should be
1074 /// appended to the existing data file whose content is at
1054 /// appended to the existing data file whose content is at
1075 /// `self.on_disk` (true), instead of written to a new data file
1055 /// `self.on_disk` (true), instead of written to a new data file
1076 /// (false).
1056 /// (false).
1077 #[timed]
1057 #[timed]
1078 fn pack_v2(
1058 fn pack_v2(
1079 &mut self,
1059 &mut self,
1080 now: Timestamp,
1060 now: Timestamp,
1081 can_append: bool,
1061 can_append: bool,
1082 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
1062 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
1083 // TODO:Β how do we want to handle this in 2038?
1063 // TODO:Β how do we want to handle this in 2038?
1084 let now: i32 = now.0.try_into().expect("time overflow");
1064 let now: i32 = now.0.try_into().expect("time overflow");
1085 let mut paths = Vec::new();
1065 let mut paths = Vec::new();
1086 for node in self.iter_nodes() {
1066 for node in self.iter_nodes() {
1087 let node = node?;
1067 let node = node?;
1088 if let Some(entry) = node.entry()? {
1068 if let Some(entry) = node.entry()? {
1089 if entry.mtime_is_ambiguous(now) {
1069 if entry.mtime_is_ambiguous(now) {
1090 paths.push(
1070 paths.push(
1091 node.full_path_borrowed(self.on_disk)?
1071 node.full_path_borrowed(self.on_disk)?
1092 .detach_from_tree(),
1072 .detach_from_tree(),
1093 )
1073 )
1094 }
1074 }
1095 }
1075 }
1096 }
1076 }
1097 // Borrow of `self` ends here since we collect cloned paths
1077 // Borrow of `self` ends here since we collect cloned paths
1098
1078
1099 self.clear_known_ambiguous_mtimes(&paths)?;
1079 self.clear_known_ambiguous_mtimes(&paths)?;
1100
1080
1101 on_disk::write(self, can_append)
1081 on_disk::write(self, can_append)
1102 }
1082 }
1103
1083
1104 fn status<'a>(
1084 fn status<'a>(
1105 &'a mut self,
1085 &'a mut self,
1106 matcher: &'a (dyn Matcher + Sync),
1086 matcher: &'a (dyn Matcher + Sync),
1107 root_dir: PathBuf,
1087 root_dir: PathBuf,
1108 ignore_files: Vec<PathBuf>,
1088 ignore_files: Vec<PathBuf>,
1109 options: StatusOptions,
1089 options: StatusOptions,
1110 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
1090 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
1111 {
1091 {
1112 super::status::status(self, matcher, root_dir, ignore_files, options)
1092 super::status::status(self, matcher, root_dir, ignore_files, options)
1113 }
1093 }
1114
1094
1115 fn copy_map_len(&self) -> usize {
1095 fn copy_map_len(&self) -> usize {
1116 self.nodes_with_copy_source_count as usize
1096 self.nodes_with_copy_source_count as usize
1117 }
1097 }
1118
1098
1119 fn copy_map_iter(&self) -> CopyMapIter<'_> {
1099 fn copy_map_iter(&self) -> CopyMapIter<'_> {
1120 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1100 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1121 Ok(if let Some(source) = node.copy_source(self.on_disk)? {
1101 Ok(if let Some(source) = node.copy_source(self.on_disk)? {
1122 Some((node.full_path(self.on_disk)?, source))
1102 Some((node.full_path(self.on_disk)?, source))
1123 } else {
1103 } else {
1124 None
1104 None
1125 })
1105 })
1126 }))
1106 }))
1127 }
1107 }
1128
1108
1129 fn copy_map_contains_key(
1109 fn copy_map_contains_key(
1130 &self,
1110 &self,
1131 key: &HgPath,
1111 key: &HgPath,
1132 ) -> Result<bool, DirstateV2ParseError> {
1112 ) -> Result<bool, DirstateV2ParseError> {
1133 Ok(if let Some(node) = self.get_node(key)? {
1113 Ok(if let Some(node) = self.get_node(key)? {
1134 node.has_copy_source()
1114 node.has_copy_source()
1135 } else {
1115 } else {
1136 false
1116 false
1137 })
1117 })
1138 }
1118 }
1139
1119
1140 fn copy_map_get(
1120 fn copy_map_get(
1141 &self,
1121 &self,
1142 key: &HgPath,
1122 key: &HgPath,
1143 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1123 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1144 if let Some(node) = self.get_node(key)? {
1124 if let Some(node) = self.get_node(key)? {
1145 if let Some(source) = node.copy_source(self.on_disk)? {
1125 if let Some(source) = node.copy_source(self.on_disk)? {
1146 return Ok(Some(source));
1126 return Ok(Some(source));
1147 }
1127 }
1148 }
1128 }
1149 Ok(None)
1129 Ok(None)
1150 }
1130 }
1151
1131
1152 fn copy_map_remove(
1132 fn copy_map_remove(
1153 &mut self,
1133 &mut self,
1154 key: &HgPath,
1134 key: &HgPath,
1155 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1135 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1156 let count = &mut self.nodes_with_copy_source_count;
1136 let count = &mut self.nodes_with_copy_source_count;
1157 let unreachable_bytes = &mut self.unreachable_bytes;
1137 let unreachable_bytes = &mut self.unreachable_bytes;
1158 Ok(Self::get_node_mut(
1138 Ok(Self::get_node_mut(
1159 self.on_disk,
1139 self.on_disk,
1160 unreachable_bytes,
1140 unreachable_bytes,
1161 &mut self.root,
1141 &mut self.root,
1162 key,
1142 key,
1163 )?
1143 )?
1164 .and_then(|node| {
1144 .and_then(|node| {
1165 if let Some(source) = &node.copy_source {
1145 if let Some(source) = &node.copy_source {
1166 *count -= 1;
1146 *count -= 1;
1167 Self::count_dropped_path(unreachable_bytes, source);
1147 Self::count_dropped_path(unreachable_bytes, source);
1168 }
1148 }
1169 node.copy_source.take().map(Cow::into_owned)
1149 node.copy_source.take().map(Cow::into_owned)
1170 }))
1150 }))
1171 }
1151 }
1172
1152
1173 fn copy_map_insert(
1153 fn copy_map_insert(
1174 &mut self,
1154 &mut self,
1175 key: HgPathBuf,
1155 key: HgPathBuf,
1176 value: HgPathBuf,
1156 value: HgPathBuf,
1177 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1157 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1178 let node = Self::get_or_insert_node(
1158 let node = Self::get_or_insert_node(
1179 self.on_disk,
1159 self.on_disk,
1180 &mut self.unreachable_bytes,
1160 &mut self.unreachable_bytes,
1181 &mut self.root,
1161 &mut self.root,
1182 &key,
1162 &key,
1183 WithBasename::to_cow_owned,
1163 WithBasename::to_cow_owned,
1184 |_ancestor| {},
1164 |_ancestor| {},
1185 )?;
1165 )?;
1186 if node.copy_source.is_none() {
1166 if node.copy_source.is_none() {
1187 self.nodes_with_copy_source_count += 1
1167 self.nodes_with_copy_source_count += 1
1188 }
1168 }
1189 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1169 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1190 }
1170 }
1191
1171
1192 fn len(&self) -> usize {
1172 fn len(&self) -> usize {
1193 self.nodes_with_entry_count as usize
1173 self.nodes_with_entry_count as usize
1194 }
1174 }
1195
1175
1196 fn contains_key(
1176 fn contains_key(
1197 &self,
1177 &self,
1198 key: &HgPath,
1178 key: &HgPath,
1199 ) -> Result<bool, DirstateV2ParseError> {
1179 ) -> Result<bool, DirstateV2ParseError> {
1200 Ok(self.get(key)?.is_some())
1180 Ok(self.get(key)?.is_some())
1201 }
1181 }
1202
1182
1203 fn get(
1183 fn get(
1204 &self,
1184 &self,
1205 key: &HgPath,
1185 key: &HgPath,
1206 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1186 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1207 Ok(if let Some(node) = self.get_node(key)? {
1187 Ok(if let Some(node) = self.get_node(key)? {
1208 node.entry()?
1188 node.entry()?
1209 } else {
1189 } else {
1210 None
1190 None
1211 })
1191 })
1212 }
1192 }
1213
1193
1214 fn iter(&self) -> StateMapIter<'_> {
1194 fn iter(&self) -> StateMapIter<'_> {
1215 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1195 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1216 Ok(if let Some(entry) = node.entry()? {
1196 Ok(if let Some(entry) = node.entry()? {
1217 Some((node.full_path(self.on_disk)?, entry))
1197 Some((node.full_path(self.on_disk)?, entry))
1218 } else {
1198 } else {
1219 None
1199 None
1220 })
1200 })
1221 }))
1201 }))
1222 }
1202 }
1223
1203
1224 fn iter_tracked_dirs(
1204 fn iter_tracked_dirs(
1225 &mut self,
1205 &mut self,
1226 ) -> Result<
1206 ) -> Result<
1227 Box<
1207 Box<
1228 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1208 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1229 + Send
1209 + Send
1230 + '_,
1210 + '_,
1231 >,
1211 >,
1232 DirstateError,
1212 DirstateError,
1233 > {
1213 > {
1234 let on_disk = self.on_disk;
1214 let on_disk = self.on_disk;
1235 Ok(Box::new(filter_map_results(
1215 Ok(Box::new(filter_map_results(
1236 self.iter_nodes(),
1216 self.iter_nodes(),
1237 move |node| {
1217 move |node| {
1238 Ok(if node.tracked_descendants_count() > 0 {
1218 Ok(if node.tracked_descendants_count() > 0 {
1239 Some(node.full_path(on_disk)?)
1219 Some(node.full_path(on_disk)?)
1240 } else {
1220 } else {
1241 None
1221 None
1242 })
1222 })
1243 },
1223 },
1244 )))
1224 )))
1245 }
1225 }
1246
1226
1247 fn debug_iter(
1227 fn debug_iter(
1248 &self,
1228 &self,
1249 all: bool,
1229 all: bool,
1250 ) -> Box<
1230 ) -> Box<
1251 dyn Iterator<
1231 dyn Iterator<
1252 Item = Result<
1232 Item = Result<
1253 (&HgPath, (u8, i32, i32, i32)),
1233 (&HgPath, (u8, i32, i32, i32)),
1254 DirstateV2ParseError,
1234 DirstateV2ParseError,
1255 >,
1235 >,
1256 > + Send
1236 > + Send
1257 + '_,
1237 + '_,
1258 > {
1238 > {
1259 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1239 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1260 let debug_tuple = if let Some(entry) = node.entry()? {
1240 let debug_tuple = if let Some(entry) = node.entry()? {
1261 entry.debug_tuple()
1241 entry.debug_tuple()
1262 } else if !all {
1242 } else if !all {
1263 return Ok(None);
1243 return Ok(None);
1264 } else if let Some(mtime) = node.cached_directory_mtime() {
1244 } else if let Some(mtime) = node.cached_directory_mtime() {
1265 (b' ', 0, -1, mtime.seconds() as i32)
1245 (b' ', 0, -1, mtime.seconds() as i32)
1266 } else {
1246 } else {
1267 (b' ', 0, -1, -1)
1247 (b' ', 0, -1, -1)
1268 };
1248 };
1269 Ok(Some((node.full_path(self.on_disk)?, debug_tuple)))
1249 Ok(Some((node.full_path(self.on_disk)?, debug_tuple)))
1270 }))
1250 }))
1271 }
1251 }
1272 }
1252 }
@@ -1,556 +1,537 b''
1 use std::path::PathBuf;
1 use std::path::PathBuf;
2
2
3 use crate::dirstate::parsers::Timestamp;
3 use crate::dirstate::parsers::Timestamp;
4 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
4 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
5 use crate::matchers::Matcher;
5 use crate::matchers::Matcher;
6 use crate::utils::hg_path::{HgPath, HgPathBuf};
6 use crate::utils::hg_path::{HgPath, HgPathBuf};
7 use crate::CopyMapIter;
7 use crate::CopyMapIter;
8 use crate::DirstateEntry;
8 use crate::DirstateEntry;
9 use crate::DirstateError;
9 use crate::DirstateError;
10 use crate::DirstateMap;
10 use crate::DirstateMap;
11 use crate::DirstateParents;
11 use crate::DirstateParents;
12 use crate::DirstateStatus;
12 use crate::DirstateStatus;
13 use crate::PatternFileWarning;
13 use crate::PatternFileWarning;
14 use crate::StateMapIter;
14 use crate::StateMapIter;
15 use crate::StatusError;
15 use crate::StatusError;
16 use crate::StatusOptions;
16 use crate::StatusOptions;
17
17
18 /// `rust/hg-cpython/src/dirstate/dirstate_map.rs` implements in Rust a
18 /// `rust/hg-cpython/src/dirstate/dirstate_map.rs` implements in Rust a
19 /// `DirstateMap` Python class that wraps `Box<dyn DirstateMapMethods + Send>`,
19 /// `DirstateMap` Python class that wraps `Box<dyn DirstateMapMethods + Send>`,
20 /// a trait object of this trait. Except for constructors, this trait defines
20 /// a trait object of this trait. Except for constructors, this trait defines
21 /// all APIs that the class needs to interact with its inner dirstate map.
21 /// all APIs that the class needs to interact with its inner dirstate map.
22 ///
22 ///
23 /// A trait object is used to support two different concrete types:
23 /// A trait object is used to support two different concrete types:
24 ///
24 ///
25 /// * `rust/hg-core/src/dirstate/dirstate_map.rs` defines the "flat dirstate
25 /// * `rust/hg-core/src/dirstate/dirstate_map.rs` defines the "flat dirstate
26 /// map" which is based on a few large `HgPath`-keyed `HashMap` and `HashSet`
26 /// map" which is based on a few large `HgPath`-keyed `HashMap` and `HashSet`
27 /// fields.
27 /// fields.
28 /// * `rust/hg-core/src/dirstate_tree/dirstate_map.rs` defines the "tree
28 /// * `rust/hg-core/src/dirstate_tree/dirstate_map.rs` defines the "tree
29 /// dirstate map" based on a tree data struture with nodes for directories
29 /// dirstate map" based on a tree data struture with nodes for directories
30 /// containing child nodes for their files and sub-directories. This tree
30 /// containing child nodes for their files and sub-directories. This tree
31 /// enables a more efficient algorithm for `hg status`, but its details are
31 /// enables a more efficient algorithm for `hg status`, but its details are
32 /// abstracted in this trait.
32 /// abstracted in this trait.
33 ///
33 ///
34 /// The dirstate map associates paths of files in the working directory to
34 /// The dirstate map associates paths of files in the working directory to
35 /// various information about the state of those files.
35 /// various information about the state of those files.
36 pub trait DirstateMapMethods {
36 pub trait DirstateMapMethods {
37 /// Remove information about all files in this map
37 /// Remove information about all files in this map
38 fn clear(&mut self);
38 fn clear(&mut self);
39
39
40 /// Add the given filename to the map if it is not already there, and
40 /// Add the given filename to the map if it is not already there, and
41 /// associate the given entry with it.
41 /// associate the given entry with it.
42 fn set_entry(
42 fn set_entry(
43 &mut self,
43 &mut self,
44 filename: &HgPath,
44 filename: &HgPath,
45 entry: DirstateEntry,
45 entry: DirstateEntry,
46 ) -> Result<(), DirstateV2ParseError>;
46 ) -> Result<(), DirstateV2ParseError>;
47
47
48 /// Add or change the information associated to a given file.
48 /// Add or change the information associated to a given file.
49 fn add_file(
49 fn add_file(
50 &mut self,
50 &mut self,
51 filename: &HgPath,
51 filename: &HgPath,
52 entry: DirstateEntry,
52 entry: DirstateEntry,
53 ) -> Result<(), DirstateError>;
53 ) -> Result<(), DirstateError>;
54
54
55 /// Mark a file as "removed" (as in `hg rm`).
55 /// Mark a file as "removed" (as in `hg rm`).
56 fn remove_file(
56 fn remove_file(
57 &mut self,
57 &mut self,
58 filename: &HgPath,
58 filename: &HgPath,
59 in_merge: bool,
59 in_merge: bool,
60 ) -> Result<(), DirstateError>;
60 ) -> Result<(), DirstateError>;
61
61
62 /// Drop information about this file from the map if any.
62 /// Drop information about this file from the map if any.
63 ///
63 ///
64 /// `get` will now return `None` for this filename.
64 /// `get` will now return `None` for this filename.
65 fn drop_entry_and_copy_source(
65 fn drop_entry_and_copy_source(
66 &mut self,
66 &mut self,
67 filename: &HgPath,
67 filename: &HgPath,
68 ) -> Result<(), DirstateError>;
68 ) -> Result<(), DirstateError>;
69
69
70 /// Among given files, mark the stored `mtime` as ambiguous if there is one
71 /// (if `state == EntryState::Normal`) equal to the given current Unix
72 /// timestamp.
73 fn clear_ambiguous_times(
74 &mut self,
75 filenames: Vec<HgPathBuf>,
76 now: i32,
77 ) -> Result<(), DirstateV2ParseError>;
78
79 /// Return whether the map has an "non-normal" entry for the given
70 /// Return whether the map has an "non-normal" entry for the given
80 /// filename. That is, any entry with a `state` other than
71 /// filename. That is, any entry with a `state` other than
81 /// `EntryState::Normal` or with an ambiguous `mtime`.
72 /// `EntryState::Normal` or with an ambiguous `mtime`.
82 fn non_normal_entries_contains(
73 fn non_normal_entries_contains(
83 &mut self,
74 &mut self,
84 key: &HgPath,
75 key: &HgPath,
85 ) -> Result<bool, DirstateV2ParseError>;
76 ) -> Result<bool, DirstateV2ParseError>;
86
77
87 /// Mark the given path as "normal" file. This is only relevant in the flat
78 /// Mark the given path as "normal" file. This is only relevant in the flat
88 /// dirstate map where there is a separate `HashSet` that needs to be kept
79 /// dirstate map where there is a separate `HashSet` that needs to be kept
89 /// up to date.
80 /// up to date.
90 /// Returns whether the key was present in the set.
81 /// Returns whether the key was present in the set.
91 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool;
82 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool;
92
83
93 /// Mark the given path as "non-normal" file.
84 /// Mark the given path as "non-normal" file.
94 /// This is only relevant in the flat dirstate map where there is a
85 /// This is only relevant in the flat dirstate map where there is a
95 /// separate `HashSet` that needs to be kept up to date.
86 /// separate `HashSet` that needs to be kept up to date.
96 fn non_normal_entries_add(&mut self, key: &HgPath);
87 fn non_normal_entries_add(&mut self, key: &HgPath);
97
88
98 /// Return an iterator of paths whose respective entry are either
89 /// Return an iterator of paths whose respective entry are either
99 /// "non-normal" (see `non_normal_entries_contains`) or "from other
90 /// "non-normal" (see `non_normal_entries_contains`) or "from other
100 /// parent".
91 /// parent".
101 ///
92 ///
102 /// If that information is cached, create the cache as needed.
93 /// If that information is cached, create the cache as needed.
103 ///
94 ///
104 /// "From other parent" is defined as `state == Normal && size == -2`.
95 /// "From other parent" is defined as `state == Normal && size == -2`.
105 ///
96 ///
106 /// Because parse errors can happen during iteration, the iterated items
97 /// Because parse errors can happen during iteration, the iterated items
107 /// are `Result`s.
98 /// are `Result`s.
108 fn non_normal_or_other_parent_paths(
99 fn non_normal_or_other_parent_paths(
109 &mut self,
100 &mut self,
110 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>;
101 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>;
111
102
112 /// Create the cache for `non_normal_or_other_parent_paths` if needed.
103 /// Create the cache for `non_normal_or_other_parent_paths` if needed.
113 ///
104 ///
114 /// If `force` is true, the cache is re-created even if it already exists.
105 /// If `force` is true, the cache is re-created even if it already exists.
115 fn set_non_normal_other_parent_entries(&mut self, force: bool);
106 fn set_non_normal_other_parent_entries(&mut self, force: bool);
116
107
117 /// Return an iterator of paths whose respective entry are "non-normal"
108 /// Return an iterator of paths whose respective entry are "non-normal"
118 /// (see `non_normal_entries_contains`).
109 /// (see `non_normal_entries_contains`).
119 ///
110 ///
120 /// If that information is cached, create the cache as needed.
111 /// If that information is cached, create the cache as needed.
121 ///
112 ///
122 /// Because parse errors can happen during iteration, the iterated items
113 /// Because parse errors can happen during iteration, the iterated items
123 /// are `Result`s.
114 /// are `Result`s.
124 fn iter_non_normal_paths(
115 fn iter_non_normal_paths(
125 &mut self,
116 &mut self,
126 ) -> Box<
117 ) -> Box<
127 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
118 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
128 >;
119 >;
129
120
130 /// Same as `iter_non_normal_paths`, but takes `&self` instead of `&mut
121 /// Same as `iter_non_normal_paths`, but takes `&self` instead of `&mut
131 /// self`.
122 /// self`.
132 ///
123 ///
133 /// Panics if a cache is necessary but does not exist yet.
124 /// Panics if a cache is necessary but does not exist yet.
134 fn iter_non_normal_paths_panic(
125 fn iter_non_normal_paths_panic(
135 &self,
126 &self,
136 ) -> Box<
127 ) -> Box<
137 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
128 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
138 >;
129 >;
139
130
140 /// Return an iterator of paths whose respective entry are "from other
131 /// Return an iterator of paths whose respective entry are "from other
141 /// parent".
132 /// parent".
142 ///
133 ///
143 /// If that information is cached, create the cache as needed.
134 /// If that information is cached, create the cache as needed.
144 ///
135 ///
145 /// "From other parent" is defined as `state == Normal && size == -2`.
136 /// "From other parent" is defined as `state == Normal && size == -2`.
146 ///
137 ///
147 /// Because parse errors can happen during iteration, the iterated items
138 /// Because parse errors can happen during iteration, the iterated items
148 /// are `Result`s.
139 /// are `Result`s.
149 fn iter_other_parent_paths(
140 fn iter_other_parent_paths(
150 &mut self,
141 &mut self,
151 ) -> Box<
142 ) -> Box<
152 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
143 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
153 >;
144 >;
154
145
155 /// Returns whether the sub-tree rooted at the given directory contains any
146 /// Returns whether the sub-tree rooted at the given directory contains any
156 /// tracked file.
147 /// tracked file.
157 ///
148 ///
158 /// A file is tracked if it has a `state` other than `EntryState::Removed`.
149 /// A file is tracked if it has a `state` other than `EntryState::Removed`.
159 fn has_tracked_dir(
150 fn has_tracked_dir(
160 &mut self,
151 &mut self,
161 directory: &HgPath,
152 directory: &HgPath,
162 ) -> Result<bool, DirstateError>;
153 ) -> Result<bool, DirstateError>;
163
154
164 /// Returns whether the sub-tree rooted at the given directory contains any
155 /// Returns whether the sub-tree rooted at the given directory contains any
165 /// file with a dirstate entry.
156 /// file with a dirstate entry.
166 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError>;
157 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError>;
167
158
168 /// Clear mtimes that are ambigous with `now` (similar to
159 /// Clear mtimes equal to `now` in entries with `state ==
169 /// `clear_ambiguous_times` but for all files in the dirstate map), and
160 /// EntryState::Normal`, and serialize bytes to write the `.hg/dirstate`
170 /// serialize bytes to write the `.hg/dirstate` file to disk in dirstate-v1
161 /// file to disk in dirstate-v1 format.
171 /// format.
172 fn pack_v1(
162 fn pack_v1(
173 &mut self,
163 &mut self,
174 parents: DirstateParents,
164 parents: DirstateParents,
175 now: Timestamp,
165 now: Timestamp,
176 ) -> Result<Vec<u8>, DirstateError>;
166 ) -> Result<Vec<u8>, DirstateError>;
177
167
178 /// Clear mtimes that are ambigous with `now` (similar to
168 /// Clear mtimes equal to `now` in entries with `state ==
179 /// `clear_ambiguous_times` but for all files in the dirstate map), and
169 /// EntryState::Normal`, and serialize bytes to write a dirstate data file
180 /// serialize bytes to write a dirstate data file to disk in dirstate-v2
170 /// to disk in dirstate-v2 format.
181 /// format.
182 ///
171 ///
183 /// Returns new data and metadata together with whether that data should be
172 /// Returns new data and metadata together with whether that data should be
184 /// appended to the existing data file whose content is at
173 /// appended to the existing data file whose content is at
185 /// `self.on_disk` (true), instead of written to a new data file
174 /// `self.on_disk` (true), instead of written to a new data file
186 /// (false).
175 /// (false).
187 ///
176 ///
188 /// Note: this is only supported by the tree dirstate map.
177 /// Note: this is only supported by the tree dirstate map.
189 fn pack_v2(
178 fn pack_v2(
190 &mut self,
179 &mut self,
191 now: Timestamp,
180 now: Timestamp,
192 can_append: bool,
181 can_append: bool,
193 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError>;
182 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError>;
194
183
195 /// Run the status algorithm.
184 /// Run the status algorithm.
196 ///
185 ///
197 /// This is not sematically a method of the dirstate map, but a different
186 /// This is not sematically a method of the dirstate map, but a different
198 /// algorithm is used for the flat v.s. tree dirstate map so having it in
187 /// algorithm is used for the flat v.s. tree dirstate map so having it in
199 /// this trait enables the same dynamic dispatch as with other methods.
188 /// this trait enables the same dynamic dispatch as with other methods.
200 fn status<'a>(
189 fn status<'a>(
201 &'a mut self,
190 &'a mut self,
202 matcher: &'a (dyn Matcher + Sync),
191 matcher: &'a (dyn Matcher + Sync),
203 root_dir: PathBuf,
192 root_dir: PathBuf,
204 ignore_files: Vec<PathBuf>,
193 ignore_files: Vec<PathBuf>,
205 options: StatusOptions,
194 options: StatusOptions,
206 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
195 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
207
196
208 /// Returns how many files in the dirstate map have a recorded copy source.
197 /// Returns how many files in the dirstate map have a recorded copy source.
209 fn copy_map_len(&self) -> usize;
198 fn copy_map_len(&self) -> usize;
210
199
211 /// Returns an iterator of `(path, copy_source)` for all files that have a
200 /// Returns an iterator of `(path, copy_source)` for all files that have a
212 /// copy source.
201 /// copy source.
213 fn copy_map_iter(&self) -> CopyMapIter<'_>;
202 fn copy_map_iter(&self) -> CopyMapIter<'_>;
214
203
215 /// Returns whether the givef file has a copy source.
204 /// Returns whether the givef file has a copy source.
216 fn copy_map_contains_key(
205 fn copy_map_contains_key(
217 &self,
206 &self,
218 key: &HgPath,
207 key: &HgPath,
219 ) -> Result<bool, DirstateV2ParseError>;
208 ) -> Result<bool, DirstateV2ParseError>;
220
209
221 /// Returns the copy source for the given file.
210 /// Returns the copy source for the given file.
222 fn copy_map_get(
211 fn copy_map_get(
223 &self,
212 &self,
224 key: &HgPath,
213 key: &HgPath,
225 ) -> Result<Option<&HgPath>, DirstateV2ParseError>;
214 ) -> Result<Option<&HgPath>, DirstateV2ParseError>;
226
215
227 /// Removes the recorded copy source if any for the given file, and returns
216 /// Removes the recorded copy source if any for the given file, and returns
228 /// it.
217 /// it.
229 fn copy_map_remove(
218 fn copy_map_remove(
230 &mut self,
219 &mut self,
231 key: &HgPath,
220 key: &HgPath,
232 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError>;
221 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError>;
233
222
234 /// Set the given `value` copy source for the given `key` file.
223 /// Set the given `value` copy source for the given `key` file.
235 fn copy_map_insert(
224 fn copy_map_insert(
236 &mut self,
225 &mut self,
237 key: HgPathBuf,
226 key: HgPathBuf,
238 value: HgPathBuf,
227 value: HgPathBuf,
239 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError>;
228 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError>;
240
229
241 /// Returns the number of files that have an entry.
230 /// Returns the number of files that have an entry.
242 fn len(&self) -> usize;
231 fn len(&self) -> usize;
243
232
244 /// Returns whether the given file has an entry.
233 /// Returns whether the given file has an entry.
245 fn contains_key(&self, key: &HgPath)
234 fn contains_key(&self, key: &HgPath)
246 -> Result<bool, DirstateV2ParseError>;
235 -> Result<bool, DirstateV2ParseError>;
247
236
248 /// Returns the entry, if any, for the given file.
237 /// Returns the entry, if any, for the given file.
249 fn get(
238 fn get(
250 &self,
239 &self,
251 key: &HgPath,
240 key: &HgPath,
252 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError>;
241 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError>;
253
242
254 /// Returns a `(path, entry)` iterator of files that have an entry.
243 /// Returns a `(path, entry)` iterator of files that have an entry.
255 ///
244 ///
256 /// Because parse errors can happen during iteration, the iterated items
245 /// Because parse errors can happen during iteration, the iterated items
257 /// are `Result`s.
246 /// are `Result`s.
258 fn iter(&self) -> StateMapIter<'_>;
247 fn iter(&self) -> StateMapIter<'_>;
259
248
260 /// Returns an iterator of tracked directories.
249 /// Returns an iterator of tracked directories.
261 ///
250 ///
262 /// This is the paths for which `has_tracked_dir` would return true.
251 /// This is the paths for which `has_tracked_dir` would return true.
263 /// Or, in other words, the union of ancestor paths of all paths that have
252 /// Or, in other words, the union of ancestor paths of all paths that have
264 /// an associated entry in a "tracked" state in this dirstate map.
253 /// an associated entry in a "tracked" state in this dirstate map.
265 ///
254 ///
266 /// Because parse errors can happen during iteration, the iterated items
255 /// Because parse errors can happen during iteration, the iterated items
267 /// are `Result`s.
256 /// are `Result`s.
268 fn iter_tracked_dirs(
257 fn iter_tracked_dirs(
269 &mut self,
258 &mut self,
270 ) -> Result<
259 ) -> Result<
271 Box<
260 Box<
272 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
261 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
273 + Send
262 + Send
274 + '_,
263 + '_,
275 >,
264 >,
276 DirstateError,
265 DirstateError,
277 >;
266 >;
278
267
279 /// Return an iterator of `(path, (state, mode, size, mtime))` for every
268 /// Return an iterator of `(path, (state, mode, size, mtime))` for every
280 /// node stored in this dirstate map, for the purpose of the `hg
269 /// node stored in this dirstate map, for the purpose of the `hg
281 /// debugdirstate` command.
270 /// debugdirstate` command.
282 ///
271 ///
283 /// If `all` is true, include nodes that don’t have an entry.
272 /// If `all` is true, include nodes that don’t have an entry.
284 /// For such nodes `state` is the ASCII space.
273 /// For such nodes `state` is the ASCII space.
285 /// An `mtime` may still be present. It is used to optimize `status`.
274 /// An `mtime` may still be present. It is used to optimize `status`.
286 ///
275 ///
287 /// Because parse errors can happen during iteration, the iterated items
276 /// Because parse errors can happen during iteration, the iterated items
288 /// are `Result`s.
277 /// are `Result`s.
289 fn debug_iter(
278 fn debug_iter(
290 &self,
279 &self,
291 all: bool,
280 all: bool,
292 ) -> Box<
281 ) -> Box<
293 dyn Iterator<
282 dyn Iterator<
294 Item = Result<
283 Item = Result<
295 (&HgPath, (u8, i32, i32, i32)),
284 (&HgPath, (u8, i32, i32, i32)),
296 DirstateV2ParseError,
285 DirstateV2ParseError,
297 >,
286 >,
298 > + Send
287 > + Send
299 + '_,
288 + '_,
300 >;
289 >;
301 }
290 }
302
291
303 impl DirstateMapMethods for DirstateMap {
292 impl DirstateMapMethods for DirstateMap {
304 fn clear(&mut self) {
293 fn clear(&mut self) {
305 self.clear()
294 self.clear()
306 }
295 }
307
296
308 /// Used to set a value directory.
297 /// Used to set a value directory.
309 ///
298 ///
310 /// XXX Is temporary during a refactor of V1 dirstate and will disappear
299 /// XXX Is temporary during a refactor of V1 dirstate and will disappear
311 /// shortly.
300 /// shortly.
312 fn set_entry(
301 fn set_entry(
313 &mut self,
302 &mut self,
314 filename: &HgPath,
303 filename: &HgPath,
315 entry: DirstateEntry,
304 entry: DirstateEntry,
316 ) -> Result<(), DirstateV2ParseError> {
305 ) -> Result<(), DirstateV2ParseError> {
317 self.set_entry(&filename, entry);
306 self.set_entry(&filename, entry);
318 Ok(())
307 Ok(())
319 }
308 }
320
309
321 fn add_file(
310 fn add_file(
322 &mut self,
311 &mut self,
323 filename: &HgPath,
312 filename: &HgPath,
324 entry: DirstateEntry,
313 entry: DirstateEntry,
325 ) -> Result<(), DirstateError> {
314 ) -> Result<(), DirstateError> {
326 self.add_file(filename, entry)
315 self.add_file(filename, entry)
327 }
316 }
328
317
329 fn remove_file(
318 fn remove_file(
330 &mut self,
319 &mut self,
331 filename: &HgPath,
320 filename: &HgPath,
332 in_merge: bool,
321 in_merge: bool,
333 ) -> Result<(), DirstateError> {
322 ) -> Result<(), DirstateError> {
334 self.remove_file(filename, in_merge)
323 self.remove_file(filename, in_merge)
335 }
324 }
336
325
337 fn drop_entry_and_copy_source(
326 fn drop_entry_and_copy_source(
338 &mut self,
327 &mut self,
339 filename: &HgPath,
328 filename: &HgPath,
340 ) -> Result<(), DirstateError> {
329 ) -> Result<(), DirstateError> {
341 self.drop_entry_and_copy_source(filename)
330 self.drop_entry_and_copy_source(filename)
342 }
331 }
343
332
344 fn clear_ambiguous_times(
345 &mut self,
346 filenames: Vec<HgPathBuf>,
347 now: i32,
348 ) -> Result<(), DirstateV2ParseError> {
349 Ok(self.clear_ambiguous_times(filenames, now))
350 }
351
352 fn non_normal_entries_contains(
333 fn non_normal_entries_contains(
353 &mut self,
334 &mut self,
354 key: &HgPath,
335 key: &HgPath,
355 ) -> Result<bool, DirstateV2ParseError> {
336 ) -> Result<bool, DirstateV2ParseError> {
356 let (non_normal, _other_parent) =
337 let (non_normal, _other_parent) =
357 self.get_non_normal_other_parent_entries();
338 self.get_non_normal_other_parent_entries();
358 Ok(non_normal.contains(key))
339 Ok(non_normal.contains(key))
359 }
340 }
360
341
361 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool {
342 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool {
362 self.non_normal_entries_remove(key)
343 self.non_normal_entries_remove(key)
363 }
344 }
364
345
365 fn non_normal_entries_add(&mut self, key: &HgPath) {
346 fn non_normal_entries_add(&mut self, key: &HgPath) {
366 self.non_normal_entries_add(key)
347 self.non_normal_entries_add(key)
367 }
348 }
368
349
369 fn non_normal_or_other_parent_paths(
350 fn non_normal_or_other_parent_paths(
370 &mut self,
351 &mut self,
371 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
352 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
372 {
353 {
373 let (non_normal, other_parent) =
354 let (non_normal, other_parent) =
374 self.get_non_normal_other_parent_entries();
355 self.get_non_normal_other_parent_entries();
375 Box::new(non_normal.union(other_parent).map(|p| Ok(&**p)))
356 Box::new(non_normal.union(other_parent).map(|p| Ok(&**p)))
376 }
357 }
377
358
378 fn set_non_normal_other_parent_entries(&mut self, force: bool) {
359 fn set_non_normal_other_parent_entries(&mut self, force: bool) {
379 self.set_non_normal_other_parent_entries(force)
360 self.set_non_normal_other_parent_entries(force)
380 }
361 }
381
362
382 fn iter_non_normal_paths(
363 fn iter_non_normal_paths(
383 &mut self,
364 &mut self,
384 ) -> Box<
365 ) -> Box<
385 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
366 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
386 > {
367 > {
387 let (non_normal, _other_parent) =
368 let (non_normal, _other_parent) =
388 self.get_non_normal_other_parent_entries();
369 self.get_non_normal_other_parent_entries();
389 Box::new(non_normal.iter().map(|p| Ok(&**p)))
370 Box::new(non_normal.iter().map(|p| Ok(&**p)))
390 }
371 }
391
372
392 fn iter_non_normal_paths_panic(
373 fn iter_non_normal_paths_panic(
393 &self,
374 &self,
394 ) -> Box<
375 ) -> Box<
395 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
376 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
396 > {
377 > {
397 let (non_normal, _other_parent) =
378 let (non_normal, _other_parent) =
398 self.get_non_normal_other_parent_entries_panic();
379 self.get_non_normal_other_parent_entries_panic();
399 Box::new(non_normal.iter().map(|p| Ok(&**p)))
380 Box::new(non_normal.iter().map(|p| Ok(&**p)))
400 }
381 }
401
382
402 fn iter_other_parent_paths(
383 fn iter_other_parent_paths(
403 &mut self,
384 &mut self,
404 ) -> Box<
385 ) -> Box<
405 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
386 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
406 > {
387 > {
407 let (_non_normal, other_parent) =
388 let (_non_normal, other_parent) =
408 self.get_non_normal_other_parent_entries();
389 self.get_non_normal_other_parent_entries();
409 Box::new(other_parent.iter().map(|p| Ok(&**p)))
390 Box::new(other_parent.iter().map(|p| Ok(&**p)))
410 }
391 }
411
392
412 fn has_tracked_dir(
393 fn has_tracked_dir(
413 &mut self,
394 &mut self,
414 directory: &HgPath,
395 directory: &HgPath,
415 ) -> Result<bool, DirstateError> {
396 ) -> Result<bool, DirstateError> {
416 self.has_tracked_dir(directory)
397 self.has_tracked_dir(directory)
417 }
398 }
418
399
419 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
400 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
420 self.has_dir(directory)
401 self.has_dir(directory)
421 }
402 }
422
403
423 fn pack_v1(
404 fn pack_v1(
424 &mut self,
405 &mut self,
425 parents: DirstateParents,
406 parents: DirstateParents,
426 now: Timestamp,
407 now: Timestamp,
427 ) -> Result<Vec<u8>, DirstateError> {
408 ) -> Result<Vec<u8>, DirstateError> {
428 self.pack(parents, now)
409 self.pack(parents, now)
429 }
410 }
430
411
431 fn pack_v2(
412 fn pack_v2(
432 &mut self,
413 &mut self,
433 _now: Timestamp,
414 _now: Timestamp,
434 _can_append: bool,
415 _can_append: bool,
435 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
416 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
436 panic!(
417 panic!(
437 "should have used dirstate_tree::DirstateMap to use the v2 format"
418 "should have used dirstate_tree::DirstateMap to use the v2 format"
438 )
419 )
439 }
420 }
440
421
441 fn status<'a>(
422 fn status<'a>(
442 &'a mut self,
423 &'a mut self,
443 matcher: &'a (dyn Matcher + Sync),
424 matcher: &'a (dyn Matcher + Sync),
444 root_dir: PathBuf,
425 root_dir: PathBuf,
445 ignore_files: Vec<PathBuf>,
426 ignore_files: Vec<PathBuf>,
446 options: StatusOptions,
427 options: StatusOptions,
447 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
428 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
448 {
429 {
449 crate::status(self, matcher, root_dir, ignore_files, options)
430 crate::status(self, matcher, root_dir, ignore_files, options)
450 }
431 }
451
432
452 fn copy_map_len(&self) -> usize {
433 fn copy_map_len(&self) -> usize {
453 self.copy_map.len()
434 self.copy_map.len()
454 }
435 }
455
436
456 fn copy_map_iter(&self) -> CopyMapIter<'_> {
437 fn copy_map_iter(&self) -> CopyMapIter<'_> {
457 Box::new(
438 Box::new(
458 self.copy_map
439 self.copy_map
459 .iter()
440 .iter()
460 .map(|(key, value)| Ok((&**key, &**value))),
441 .map(|(key, value)| Ok((&**key, &**value))),
461 )
442 )
462 }
443 }
463
444
464 fn copy_map_contains_key(
445 fn copy_map_contains_key(
465 &self,
446 &self,
466 key: &HgPath,
447 key: &HgPath,
467 ) -> Result<bool, DirstateV2ParseError> {
448 ) -> Result<bool, DirstateV2ParseError> {
468 Ok(self.copy_map.contains_key(key))
449 Ok(self.copy_map.contains_key(key))
469 }
450 }
470
451
471 fn copy_map_get(
452 fn copy_map_get(
472 &self,
453 &self,
473 key: &HgPath,
454 key: &HgPath,
474 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
455 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
475 Ok(self.copy_map.get(key).map(|p| &**p))
456 Ok(self.copy_map.get(key).map(|p| &**p))
476 }
457 }
477
458
478 fn copy_map_remove(
459 fn copy_map_remove(
479 &mut self,
460 &mut self,
480 key: &HgPath,
461 key: &HgPath,
481 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
462 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
482 Ok(self.copy_map.remove(key))
463 Ok(self.copy_map.remove(key))
483 }
464 }
484
465
485 fn copy_map_insert(
466 fn copy_map_insert(
486 &mut self,
467 &mut self,
487 key: HgPathBuf,
468 key: HgPathBuf,
488 value: HgPathBuf,
469 value: HgPathBuf,
489 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
470 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
490 Ok(self.copy_map.insert(key, value))
471 Ok(self.copy_map.insert(key, value))
491 }
472 }
492
473
493 fn len(&self) -> usize {
474 fn len(&self) -> usize {
494 (&**self).len()
475 (&**self).len()
495 }
476 }
496
477
497 fn contains_key(
478 fn contains_key(
498 &self,
479 &self,
499 key: &HgPath,
480 key: &HgPath,
500 ) -> Result<bool, DirstateV2ParseError> {
481 ) -> Result<bool, DirstateV2ParseError> {
501 Ok((&**self).contains_key(key))
482 Ok((&**self).contains_key(key))
502 }
483 }
503
484
504 fn get(
485 fn get(
505 &self,
486 &self,
506 key: &HgPath,
487 key: &HgPath,
507 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
488 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
508 Ok((&**self).get(key).cloned())
489 Ok((&**self).get(key).cloned())
509 }
490 }
510
491
511 fn iter(&self) -> StateMapIter<'_> {
492 fn iter(&self) -> StateMapIter<'_> {
512 Box::new((&**self).iter().map(|(key, value)| Ok((&**key, *value))))
493 Box::new((&**self).iter().map(|(key, value)| Ok((&**key, *value))))
513 }
494 }
514
495
515 fn iter_tracked_dirs(
496 fn iter_tracked_dirs(
516 &mut self,
497 &mut self,
517 ) -> Result<
498 ) -> Result<
518 Box<
499 Box<
519 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
500 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
520 + Send
501 + Send
521 + '_,
502 + '_,
522 >,
503 >,
523 DirstateError,
504 DirstateError,
524 > {
505 > {
525 self.set_all_dirs()?;
506 self.set_all_dirs()?;
526 Ok(Box::new(
507 Ok(Box::new(
527 self.all_dirs
508 self.all_dirs
528 .as_ref()
509 .as_ref()
529 .unwrap()
510 .unwrap()
530 .iter()
511 .iter()
531 .map(|path| Ok(&**path)),
512 .map(|path| Ok(&**path)),
532 ))
513 ))
533 }
514 }
534
515
535 fn debug_iter(
516 fn debug_iter(
536 &self,
517 &self,
537 all: bool,
518 all: bool,
538 ) -> Box<
519 ) -> Box<
539 dyn Iterator<
520 dyn Iterator<
540 Item = Result<
521 Item = Result<
541 (&HgPath, (u8, i32, i32, i32)),
522 (&HgPath, (u8, i32, i32, i32)),
542 DirstateV2ParseError,
523 DirstateV2ParseError,
543 >,
524 >,
544 > + Send
525 > + Send
545 + '_,
526 + '_,
546 > {
527 > {
547 // Not used for the flat (not tree-based) DirstateMap
528 // Not used for the flat (not tree-based) DirstateMap
548 let _ = all;
529 let _ = all;
549
530
550 Box::new(
531 Box::new(
551 (&**self)
532 (&**self)
552 .iter()
533 .iter()
553 .map(|(path, entry)| Ok((&**path, entry.debug_tuple()))),
534 .map(|(path, entry)| Ok((&**path, entry.debug_tuple()))),
554 )
535 )
555 }
536 }
556 }
537 }
@@ -1,237 +1,229 b''
1 use crate::dirstate::parsers::Timestamp;
1 use crate::dirstate::parsers::Timestamp;
2 use crate::dirstate_tree::dispatch::DirstateMapMethods;
2 use crate::dirstate_tree::dispatch::DirstateMapMethods;
3 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
3 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
4 use crate::dirstate_tree::owning::OwningDirstateMap;
4 use crate::dirstate_tree::owning::OwningDirstateMap;
5 use crate::matchers::Matcher;
5 use crate::matchers::Matcher;
6 use crate::utils::hg_path::{HgPath, HgPathBuf};
6 use crate::utils::hg_path::{HgPath, HgPathBuf};
7 use crate::CopyMapIter;
7 use crate::CopyMapIter;
8 use crate::DirstateEntry;
8 use crate::DirstateEntry;
9 use crate::DirstateError;
9 use crate::DirstateError;
10 use crate::DirstateParents;
10 use crate::DirstateParents;
11 use crate::DirstateStatus;
11 use crate::DirstateStatus;
12 use crate::PatternFileWarning;
12 use crate::PatternFileWarning;
13 use crate::StateMapIter;
13 use crate::StateMapIter;
14 use crate::StatusError;
14 use crate::StatusError;
15 use crate::StatusOptions;
15 use crate::StatusOptions;
16 use std::path::PathBuf;
16 use std::path::PathBuf;
17
17
18 impl DirstateMapMethods for OwningDirstateMap {
18 impl DirstateMapMethods for OwningDirstateMap {
19 fn clear(&mut self) {
19 fn clear(&mut self) {
20 self.get_mut().clear()
20 self.get_mut().clear()
21 }
21 }
22
22
23 fn set_entry(
23 fn set_entry(
24 &mut self,
24 &mut self,
25 filename: &HgPath,
25 filename: &HgPath,
26 entry: DirstateEntry,
26 entry: DirstateEntry,
27 ) -> Result<(), DirstateV2ParseError> {
27 ) -> Result<(), DirstateV2ParseError> {
28 self.get_mut().set_entry(filename, entry)
28 self.get_mut().set_entry(filename, entry)
29 }
29 }
30
30
31 fn add_file(
31 fn add_file(
32 &mut self,
32 &mut self,
33 filename: &HgPath,
33 filename: &HgPath,
34 entry: DirstateEntry,
34 entry: DirstateEntry,
35 ) -> Result<(), DirstateError> {
35 ) -> Result<(), DirstateError> {
36 self.get_mut().add_file(filename, entry)
36 self.get_mut().add_file(filename, entry)
37 }
37 }
38
38
39 fn remove_file(
39 fn remove_file(
40 &mut self,
40 &mut self,
41 filename: &HgPath,
41 filename: &HgPath,
42 in_merge: bool,
42 in_merge: bool,
43 ) -> Result<(), DirstateError> {
43 ) -> Result<(), DirstateError> {
44 self.get_mut().remove_file(filename, in_merge)
44 self.get_mut().remove_file(filename, in_merge)
45 }
45 }
46
46
47 fn drop_entry_and_copy_source(
47 fn drop_entry_and_copy_source(
48 &mut self,
48 &mut self,
49 filename: &HgPath,
49 filename: &HgPath,
50 ) -> Result<(), DirstateError> {
50 ) -> Result<(), DirstateError> {
51 self.get_mut().drop_entry_and_copy_source(filename)
51 self.get_mut().drop_entry_and_copy_source(filename)
52 }
52 }
53
53
54 fn clear_ambiguous_times(
55 &mut self,
56 filenames: Vec<HgPathBuf>,
57 now: i32,
58 ) -> Result<(), DirstateV2ParseError> {
59 self.get_mut().clear_ambiguous_times(filenames, now)
60 }
61
62 fn non_normal_entries_contains(
54 fn non_normal_entries_contains(
63 &mut self,
55 &mut self,
64 key: &HgPath,
56 key: &HgPath,
65 ) -> Result<bool, DirstateV2ParseError> {
57 ) -> Result<bool, DirstateV2ParseError> {
66 self.get_mut().non_normal_entries_contains(key)
58 self.get_mut().non_normal_entries_contains(key)
67 }
59 }
68
60
69 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool {
61 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool {
70 self.get_mut().non_normal_entries_remove(key)
62 self.get_mut().non_normal_entries_remove(key)
71 }
63 }
72
64
73 fn non_normal_entries_add(&mut self, key: &HgPath) {
65 fn non_normal_entries_add(&mut self, key: &HgPath) {
74 self.get_mut().non_normal_entries_add(key)
66 self.get_mut().non_normal_entries_add(key)
75 }
67 }
76
68
77 fn non_normal_or_other_parent_paths(
69 fn non_normal_or_other_parent_paths(
78 &mut self,
70 &mut self,
79 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
71 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
80 {
72 {
81 self.get_mut().non_normal_or_other_parent_paths()
73 self.get_mut().non_normal_or_other_parent_paths()
82 }
74 }
83
75
84 fn set_non_normal_other_parent_entries(&mut self, force: bool) {
76 fn set_non_normal_other_parent_entries(&mut self, force: bool) {
85 self.get_mut().set_non_normal_other_parent_entries(force)
77 self.get_mut().set_non_normal_other_parent_entries(force)
86 }
78 }
87
79
88 fn iter_non_normal_paths(
80 fn iter_non_normal_paths(
89 &mut self,
81 &mut self,
90 ) -> Box<
82 ) -> Box<
91 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
83 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
92 > {
84 > {
93 self.get_mut().iter_non_normal_paths()
85 self.get_mut().iter_non_normal_paths()
94 }
86 }
95
87
96 fn iter_non_normal_paths_panic(
88 fn iter_non_normal_paths_panic(
97 &self,
89 &self,
98 ) -> Box<
90 ) -> Box<
99 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
91 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
100 > {
92 > {
101 self.get().iter_non_normal_paths_panic()
93 self.get().iter_non_normal_paths_panic()
102 }
94 }
103
95
104 fn iter_other_parent_paths(
96 fn iter_other_parent_paths(
105 &mut self,
97 &mut self,
106 ) -> Box<
98 ) -> Box<
107 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
99 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
108 > {
100 > {
109 self.get_mut().iter_other_parent_paths()
101 self.get_mut().iter_other_parent_paths()
110 }
102 }
111
103
112 fn has_tracked_dir(
104 fn has_tracked_dir(
113 &mut self,
105 &mut self,
114 directory: &HgPath,
106 directory: &HgPath,
115 ) -> Result<bool, DirstateError> {
107 ) -> Result<bool, DirstateError> {
116 self.get_mut().has_tracked_dir(directory)
108 self.get_mut().has_tracked_dir(directory)
117 }
109 }
118
110
119 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
111 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
120 self.get_mut().has_dir(directory)
112 self.get_mut().has_dir(directory)
121 }
113 }
122
114
123 fn pack_v1(
115 fn pack_v1(
124 &mut self,
116 &mut self,
125 parents: DirstateParents,
117 parents: DirstateParents,
126 now: Timestamp,
118 now: Timestamp,
127 ) -> Result<Vec<u8>, DirstateError> {
119 ) -> Result<Vec<u8>, DirstateError> {
128 self.get_mut().pack_v1(parents, now)
120 self.get_mut().pack_v1(parents, now)
129 }
121 }
130
122
131 fn pack_v2(
123 fn pack_v2(
132 &mut self,
124 &mut self,
133 now: Timestamp,
125 now: Timestamp,
134 can_append: bool,
126 can_append: bool,
135 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
127 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
136 self.get_mut().pack_v2(now, can_append)
128 self.get_mut().pack_v2(now, can_append)
137 }
129 }
138
130
139 fn status<'a>(
131 fn status<'a>(
140 &'a mut self,
132 &'a mut self,
141 matcher: &'a (dyn Matcher + Sync),
133 matcher: &'a (dyn Matcher + Sync),
142 root_dir: PathBuf,
134 root_dir: PathBuf,
143 ignore_files: Vec<PathBuf>,
135 ignore_files: Vec<PathBuf>,
144 options: StatusOptions,
136 options: StatusOptions,
145 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
137 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
146 {
138 {
147 self.get_mut()
139 self.get_mut()
148 .status(matcher, root_dir, ignore_files, options)
140 .status(matcher, root_dir, ignore_files, options)
149 }
141 }
150
142
151 fn copy_map_len(&self) -> usize {
143 fn copy_map_len(&self) -> usize {
152 self.get().copy_map_len()
144 self.get().copy_map_len()
153 }
145 }
154
146
155 fn copy_map_iter(&self) -> CopyMapIter<'_> {
147 fn copy_map_iter(&self) -> CopyMapIter<'_> {
156 self.get().copy_map_iter()
148 self.get().copy_map_iter()
157 }
149 }
158
150
159 fn copy_map_contains_key(
151 fn copy_map_contains_key(
160 &self,
152 &self,
161 key: &HgPath,
153 key: &HgPath,
162 ) -> Result<bool, DirstateV2ParseError> {
154 ) -> Result<bool, DirstateV2ParseError> {
163 self.get().copy_map_contains_key(key)
155 self.get().copy_map_contains_key(key)
164 }
156 }
165
157
166 fn copy_map_get(
158 fn copy_map_get(
167 &self,
159 &self,
168 key: &HgPath,
160 key: &HgPath,
169 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
161 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
170 self.get().copy_map_get(key)
162 self.get().copy_map_get(key)
171 }
163 }
172
164
173 fn copy_map_remove(
165 fn copy_map_remove(
174 &mut self,
166 &mut self,
175 key: &HgPath,
167 key: &HgPath,
176 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
168 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
177 self.get_mut().copy_map_remove(key)
169 self.get_mut().copy_map_remove(key)
178 }
170 }
179
171
180 fn copy_map_insert(
172 fn copy_map_insert(
181 &mut self,
173 &mut self,
182 key: HgPathBuf,
174 key: HgPathBuf,
183 value: HgPathBuf,
175 value: HgPathBuf,
184 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
176 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
185 self.get_mut().copy_map_insert(key, value)
177 self.get_mut().copy_map_insert(key, value)
186 }
178 }
187
179
188 fn len(&self) -> usize {
180 fn len(&self) -> usize {
189 self.get().len()
181 self.get().len()
190 }
182 }
191
183
192 fn contains_key(
184 fn contains_key(
193 &self,
185 &self,
194 key: &HgPath,
186 key: &HgPath,
195 ) -> Result<bool, DirstateV2ParseError> {
187 ) -> Result<bool, DirstateV2ParseError> {
196 self.get().contains_key(key)
188 self.get().contains_key(key)
197 }
189 }
198
190
199 fn get(
191 fn get(
200 &self,
192 &self,
201 key: &HgPath,
193 key: &HgPath,
202 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
194 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
203 self.get().get(key)
195 self.get().get(key)
204 }
196 }
205
197
206 fn iter(&self) -> StateMapIter<'_> {
198 fn iter(&self) -> StateMapIter<'_> {
207 self.get().iter()
199 self.get().iter()
208 }
200 }
209
201
210 fn iter_tracked_dirs(
202 fn iter_tracked_dirs(
211 &mut self,
203 &mut self,
212 ) -> Result<
204 ) -> Result<
213 Box<
205 Box<
214 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
206 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
215 + Send
207 + Send
216 + '_,
208 + '_,
217 >,
209 >,
218 DirstateError,
210 DirstateError,
219 > {
211 > {
220 self.get_mut().iter_tracked_dirs()
212 self.get_mut().iter_tracked_dirs()
221 }
213 }
222
214
223 fn debug_iter(
215 fn debug_iter(
224 &self,
216 &self,
225 all: bool,
217 all: bool,
226 ) -> Box<
218 ) -> Box<
227 dyn Iterator<
219 dyn Iterator<
228 Item = Result<
220 Item = Result<
229 (&HgPath, (u8, i32, i32, i32)),
221 (&HgPath, (u8, i32, i32, i32)),
230 DirstateV2ParseError,
222 DirstateV2ParseError,
231 >,
223 >,
232 > + Send
224 > + Send
233 + '_,
225 + '_,
234 > {
226 > {
235 self.get().debug_iter(all)
227 self.get().debug_iter(all)
236 }
228 }
237 }
229 }
@@ -1,632 +1,612 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, ObjectProtocol, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList,
15 exc, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, PyNone, PyObject,
16 PyNone, PyObject, PyResult, PySet, PyString, Python, PythonObject,
16 PyResult, PySet, PyString, Python, PythonObject, ToPyObject,
17 ToPyObject, UnsafePyLeaked,
17 UnsafePyLeaked,
18 };
18 };
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 dirstate::non_normal_entries::{
23 dirstate::non_normal_entries::{
24 NonNormalEntries, NonNormalEntriesIterator,
24 NonNormalEntries, NonNormalEntriesIterator,
25 },
25 },
26 pybytes_deref::PyBytesDeref,
26 pybytes_deref::PyBytesDeref,
27 };
27 };
28 use hg::{
28 use hg::{
29 dirstate::parsers::Timestamp,
29 dirstate::parsers::Timestamp,
30 dirstate_tree::dirstate_map::DirstateMap as TreeDirstateMap,
30 dirstate_tree::dirstate_map::DirstateMap as TreeDirstateMap,
31 dirstate_tree::dispatch::DirstateMapMethods,
31 dirstate_tree::dispatch::DirstateMapMethods,
32 dirstate_tree::on_disk::DirstateV2ParseError,
32 dirstate_tree::on_disk::DirstateV2ParseError,
33 dirstate_tree::owning::OwningDirstateMap,
33 dirstate_tree::owning::OwningDirstateMap,
34 revlog::Node,
34 revlog::Node,
35 utils::files::normalize_case,
35 utils::files::normalize_case,
36 utils::hg_path::{HgPath, HgPathBuf},
36 utils::hg_path::{HgPath, HgPathBuf},
37 DirstateEntry, DirstateError, DirstateMap as RustDirstateMap,
37 DirstateEntry, DirstateError, DirstateMap as RustDirstateMap,
38 DirstateParents, EntryState, StateMapIter,
38 DirstateParents, EntryState, StateMapIter,
39 };
39 };
40
40
41 // TODO
41 // TODO
42 // This object needs to share references to multiple members of its Rust
42 // This object needs to share references to multiple members of its Rust
43 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
43 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
44 // Right now `CopyMap` is done, but it needs to have an explicit reference
44 // Right now `CopyMap` is done, but it needs to have an explicit reference
45 // to `RustDirstateMap` which itself needs to have an encapsulation for
45 // to `RustDirstateMap` which itself needs to have an encapsulation for
46 // every method in `CopyMap` (copymapcopy, etc.).
46 // every method in `CopyMap` (copymapcopy, etc.).
47 // This is ugly and hard to maintain.
47 // This is ugly and hard to maintain.
48 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
48 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
49 // `py_class!` is already implemented and does not mention
49 // `py_class!` is already implemented and does not mention
50 // `RustDirstateMap`, rightfully so.
50 // `RustDirstateMap`, rightfully so.
51 // All attributes also have to have a separate refcount data attribute for
51 // All attributes also have to have a separate refcount data attribute for
52 // leaks, with all methods that go along for reference sharing.
52 // leaks, with all methods that go along for reference sharing.
53 py_class!(pub class DirstateMap |py| {
53 py_class!(pub class DirstateMap |py| {
54 @shared data inner: Box<dyn DirstateMapMethods + Send>;
54 @shared data inner: Box<dyn DirstateMapMethods + Send>;
55
55
56 /// Returns a `(dirstate_map, parents)` tuple
56 /// Returns a `(dirstate_map, parents)` tuple
57 @staticmethod
57 @staticmethod
58 def new_v1(
58 def new_v1(
59 use_dirstate_tree: bool,
59 use_dirstate_tree: bool,
60 on_disk: PyBytes,
60 on_disk: PyBytes,
61 ) -> PyResult<PyObject> {
61 ) -> PyResult<PyObject> {
62 let (inner, parents) = if use_dirstate_tree {
62 let (inner, parents) = if use_dirstate_tree {
63 let on_disk = PyBytesDeref::new(py, on_disk);
63 let on_disk = PyBytesDeref::new(py, on_disk);
64 let mut map = OwningDirstateMap::new_empty(on_disk);
64 let mut map = OwningDirstateMap::new_empty(on_disk);
65 let (on_disk, map_placeholder) = map.get_mut_pair();
65 let (on_disk, map_placeholder) = map.get_mut_pair();
66
66
67 let (actual_map, parents) = TreeDirstateMap::new_v1(on_disk)
67 let (actual_map, parents) = TreeDirstateMap::new_v1(on_disk)
68 .map_err(|e| dirstate_error(py, e))?;
68 .map_err(|e| dirstate_error(py, e))?;
69 *map_placeholder = actual_map;
69 *map_placeholder = actual_map;
70 (Box::new(map) as _, parents)
70 (Box::new(map) as _, parents)
71 } else {
71 } else {
72 let bytes = on_disk.data(py);
72 let bytes = on_disk.data(py);
73 let mut map = RustDirstateMap::default();
73 let mut map = RustDirstateMap::default();
74 let parents = map.read(bytes).map_err(|e| dirstate_error(py, e))?;
74 let parents = map.read(bytes).map_err(|e| dirstate_error(py, e))?;
75 (Box::new(map) as _, parents)
75 (Box::new(map) as _, parents)
76 };
76 };
77 let map = Self::create_instance(py, inner)?;
77 let map = Self::create_instance(py, inner)?;
78 let parents = parents.map(|p| {
78 let parents = parents.map(|p| {
79 let p1 = PyBytes::new(py, p.p1.as_bytes());
79 let p1 = PyBytes::new(py, p.p1.as_bytes());
80 let p2 = PyBytes::new(py, p.p2.as_bytes());
80 let p2 = PyBytes::new(py, p.p2.as_bytes());
81 (p1, p2)
81 (p1, p2)
82 });
82 });
83 Ok((map, parents).to_py_object(py).into_object())
83 Ok((map, parents).to_py_object(py).into_object())
84 }
84 }
85
85
86 /// Returns a DirstateMap
86 /// Returns a DirstateMap
87 @staticmethod
87 @staticmethod
88 def new_v2(
88 def new_v2(
89 on_disk: PyBytes,
89 on_disk: PyBytes,
90 data_size: usize,
90 data_size: usize,
91 tree_metadata: PyBytes,
91 tree_metadata: PyBytes,
92 ) -> PyResult<PyObject> {
92 ) -> PyResult<PyObject> {
93 let dirstate_error = |e: DirstateError| {
93 let dirstate_error = |e: DirstateError| {
94 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
94 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
95 };
95 };
96 let on_disk = PyBytesDeref::new(py, on_disk);
96 let on_disk = PyBytesDeref::new(py, on_disk);
97 let mut map = OwningDirstateMap::new_empty(on_disk);
97 let mut map = OwningDirstateMap::new_empty(on_disk);
98 let (on_disk, map_placeholder) = map.get_mut_pair();
98 let (on_disk, map_placeholder) = map.get_mut_pair();
99 *map_placeholder = TreeDirstateMap::new_v2(
99 *map_placeholder = TreeDirstateMap::new_v2(
100 on_disk, data_size, tree_metadata.data(py),
100 on_disk, data_size, tree_metadata.data(py),
101 ).map_err(dirstate_error)?;
101 ).map_err(dirstate_error)?;
102 let map = Self::create_instance(py, Box::new(map))?;
102 let map = Self::create_instance(py, Box::new(map))?;
103 Ok(map.into_object())
103 Ok(map.into_object())
104 }
104 }
105
105
106 def clear(&self) -> PyResult<PyObject> {
106 def clear(&self) -> PyResult<PyObject> {
107 self.inner(py).borrow_mut().clear();
107 self.inner(py).borrow_mut().clear();
108 Ok(py.None())
108 Ok(py.None())
109 }
109 }
110
110
111 def get(
111 def get(
112 &self,
112 &self,
113 key: PyObject,
113 key: PyObject,
114 default: Option<PyObject> = None
114 default: Option<PyObject> = None
115 ) -> PyResult<Option<PyObject>> {
115 ) -> PyResult<Option<PyObject>> {
116 let key = key.extract::<PyBytes>(py)?;
116 let key = key.extract::<PyBytes>(py)?;
117 match self
117 match self
118 .inner(py)
118 .inner(py)
119 .borrow()
119 .borrow()
120 .get(HgPath::new(key.data(py)))
120 .get(HgPath::new(key.data(py)))
121 .map_err(|e| v2_error(py, e))?
121 .map_err(|e| v2_error(py, e))?
122 {
122 {
123 Some(entry) => {
123 Some(entry) => {
124 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
124 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
125 },
125 },
126 None => Ok(default)
126 None => Ok(default)
127 }
127 }
128 }
128 }
129
129
130 def set_dirstate_item(
130 def set_dirstate_item(
131 &self,
131 &self,
132 path: PyObject,
132 path: PyObject,
133 item: DirstateItem
133 item: DirstateItem
134 ) -> PyResult<PyObject> {
134 ) -> PyResult<PyObject> {
135 let f = path.extract::<PyBytes>(py)?;
135 let f = path.extract::<PyBytes>(py)?;
136 let filename = HgPath::new(f.data(py));
136 let filename = HgPath::new(f.data(py));
137 self.inner(py)
137 self.inner(py)
138 .borrow_mut()
138 .borrow_mut()
139 .set_entry(filename, item.get_entry(py))
139 .set_entry(filename, item.get_entry(py))
140 .map_err(|e| v2_error(py, e))?;
140 .map_err(|e| v2_error(py, e))?;
141 Ok(py.None())
141 Ok(py.None())
142 }
142 }
143
143
144 def addfile(
144 def addfile(
145 &self,
145 &self,
146 f: PyBytes,
146 f: PyBytes,
147 item: DirstateItem,
147 item: DirstateItem,
148 ) -> PyResult<PyNone> {
148 ) -> PyResult<PyNone> {
149 let filename = HgPath::new(f.data(py));
149 let filename = HgPath::new(f.data(py));
150 let entry = item.get_entry(py);
150 let entry = item.get_entry(py);
151 self.inner(py)
151 self.inner(py)
152 .borrow_mut()
152 .borrow_mut()
153 .add_file(filename, entry)
153 .add_file(filename, entry)
154 .map_err(|e |dirstate_error(py, e))?;
154 .map_err(|e |dirstate_error(py, e))?;
155 Ok(PyNone)
155 Ok(PyNone)
156 }
156 }
157
157
158 def removefile(
158 def removefile(
159 &self,
159 &self,
160 f: PyObject,
160 f: PyObject,
161 in_merge: PyObject
161 in_merge: PyObject
162 ) -> PyResult<PyObject> {
162 ) -> PyResult<PyObject> {
163 self.inner(py).borrow_mut()
163 self.inner(py).borrow_mut()
164 .remove_file(
164 .remove_file(
165 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
165 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
166 in_merge.extract::<PyBool>(py)?.is_true(),
166 in_merge.extract::<PyBool>(py)?.is_true(),
167 )
167 )
168 .or_else(|_| {
168 .or_else(|_| {
169 Err(PyErr::new::<exc::OSError, _>(
169 Err(PyErr::new::<exc::OSError, _>(
170 py,
170 py,
171 "Dirstate error".to_string(),
171 "Dirstate error".to_string(),
172 ))
172 ))
173 })?;
173 })?;
174 Ok(py.None())
174 Ok(py.None())
175 }
175 }
176
176
177 def drop_item_and_copy_source(
177 def drop_item_and_copy_source(
178 &self,
178 &self,
179 f: PyBytes,
179 f: PyBytes,
180 ) -> PyResult<PyNone> {
180 ) -> PyResult<PyNone> {
181 self.inner(py)
181 self.inner(py)
182 .borrow_mut()
182 .borrow_mut()
183 .drop_entry_and_copy_source(HgPath::new(f.data(py)))
183 .drop_entry_and_copy_source(HgPath::new(f.data(py)))
184 .map_err(|e |dirstate_error(py, e))?;
184 .map_err(|e |dirstate_error(py, e))?;
185 Ok(PyNone)
185 Ok(PyNone)
186 }
186 }
187
187
188 def clearambiguoustimes(
189 &self,
190 files: PyObject,
191 now: PyObject
192 ) -> PyResult<PyObject> {
193 let files: PyResult<Vec<HgPathBuf>> = files
194 .iter(py)?
195 .map(|filename| {
196 Ok(HgPathBuf::from_bytes(
197 filename?.extract::<PyBytes>(py)?.data(py),
198 ))
199 })
200 .collect();
201 self.inner(py)
202 .borrow_mut()
203 .clear_ambiguous_times(files?, now.extract(py)?)
204 .map_err(|e| v2_error(py, e))?;
205 Ok(py.None())
206 }
207
208 def other_parent_entries(&self) -> PyResult<PyObject> {
188 def other_parent_entries(&self) -> PyResult<PyObject> {
209 let mut inner_shared = self.inner(py).borrow_mut();
189 let mut inner_shared = self.inner(py).borrow_mut();
210 let set = PySet::empty(py)?;
190 let set = PySet::empty(py)?;
211 for path in inner_shared.iter_other_parent_paths() {
191 for path in inner_shared.iter_other_parent_paths() {
212 let path = path.map_err(|e| v2_error(py, e))?;
192 let path = path.map_err(|e| v2_error(py, e))?;
213 set.add(py, PyBytes::new(py, path.as_bytes()))?;
193 set.add(py, PyBytes::new(py, path.as_bytes()))?;
214 }
194 }
215 Ok(set.into_object())
195 Ok(set.into_object())
216 }
196 }
217
197
218 def non_normal_entries(&self) -> PyResult<NonNormalEntries> {
198 def non_normal_entries(&self) -> PyResult<NonNormalEntries> {
219 NonNormalEntries::from_inner(py, self.clone_ref(py))
199 NonNormalEntries::from_inner(py, self.clone_ref(py))
220 }
200 }
221
201
222 def non_normal_entries_contains(&self, key: PyObject) -> PyResult<bool> {
202 def non_normal_entries_contains(&self, key: PyObject) -> PyResult<bool> {
223 let key = key.extract::<PyBytes>(py)?;
203 let key = key.extract::<PyBytes>(py)?;
224 self.inner(py)
204 self.inner(py)
225 .borrow_mut()
205 .borrow_mut()
226 .non_normal_entries_contains(HgPath::new(key.data(py)))
206 .non_normal_entries_contains(HgPath::new(key.data(py)))
227 .map_err(|e| v2_error(py, e))
207 .map_err(|e| v2_error(py, e))
228 }
208 }
229
209
230 def non_normal_entries_display(&self) -> PyResult<PyString> {
210 def non_normal_entries_display(&self) -> PyResult<PyString> {
231 let mut inner = self.inner(py).borrow_mut();
211 let mut inner = self.inner(py).borrow_mut();
232 let paths = inner
212 let paths = inner
233 .iter_non_normal_paths()
213 .iter_non_normal_paths()
234 .collect::<Result<Vec<_>, _>>()
214 .collect::<Result<Vec<_>, _>>()
235 .map_err(|e| v2_error(py, e))?;
215 .map_err(|e| v2_error(py, e))?;
236 let formatted = format!("NonNormalEntries: {}", hg::utils::join_display(paths, ", "));
216 let formatted = format!("NonNormalEntries: {}", hg::utils::join_display(paths, ", "));
237 Ok(PyString::new(py, &formatted))
217 Ok(PyString::new(py, &formatted))
238 }
218 }
239
219
240 def non_normal_entries_remove(&self, key: PyObject) -> PyResult<PyObject> {
220 def non_normal_entries_remove(&self, key: PyObject) -> PyResult<PyObject> {
241 let key = key.extract::<PyBytes>(py)?;
221 let key = key.extract::<PyBytes>(py)?;
242 let key = key.data(py);
222 let key = key.data(py);
243 let was_present = self
223 let was_present = self
244 .inner(py)
224 .inner(py)
245 .borrow_mut()
225 .borrow_mut()
246 .non_normal_entries_remove(HgPath::new(key));
226 .non_normal_entries_remove(HgPath::new(key));
247 if !was_present {
227 if !was_present {
248 let msg = String::from_utf8_lossy(key);
228 let msg = String::from_utf8_lossy(key);
249 Err(PyErr::new::<exc::KeyError, _>(py, msg))
229 Err(PyErr::new::<exc::KeyError, _>(py, msg))
250 } else {
230 } else {
251 Ok(py.None())
231 Ok(py.None())
252 }
232 }
253 }
233 }
254
234
255 def non_normal_entries_discard(&self, key: PyObject) -> PyResult<PyObject>
235 def non_normal_entries_discard(&self, key: PyObject) -> PyResult<PyObject>
256 {
236 {
257 let key = key.extract::<PyBytes>(py)?;
237 let key = key.extract::<PyBytes>(py)?;
258 self
238 self
259 .inner(py)
239 .inner(py)
260 .borrow_mut()
240 .borrow_mut()
261 .non_normal_entries_remove(HgPath::new(key.data(py)));
241 .non_normal_entries_remove(HgPath::new(key.data(py)));
262 Ok(py.None())
242 Ok(py.None())
263 }
243 }
264
244
265 def non_normal_entries_add(&self, key: PyObject) -> PyResult<PyObject> {
245 def non_normal_entries_add(&self, key: PyObject) -> PyResult<PyObject> {
266 let key = key.extract::<PyBytes>(py)?;
246 let key = key.extract::<PyBytes>(py)?;
267 self
247 self
268 .inner(py)
248 .inner(py)
269 .borrow_mut()
249 .borrow_mut()
270 .non_normal_entries_add(HgPath::new(key.data(py)));
250 .non_normal_entries_add(HgPath::new(key.data(py)));
271 Ok(py.None())
251 Ok(py.None())
272 }
252 }
273
253
274 def non_normal_or_other_parent_paths(&self) -> PyResult<PyList> {
254 def non_normal_or_other_parent_paths(&self) -> PyResult<PyList> {
275 let mut inner = self.inner(py).borrow_mut();
255 let mut inner = self.inner(py).borrow_mut();
276
256
277 let ret = PyList::new(py, &[]);
257 let ret = PyList::new(py, &[]);
278 for filename in inner.non_normal_or_other_parent_paths() {
258 for filename in inner.non_normal_or_other_parent_paths() {
279 let filename = filename.map_err(|e| v2_error(py, e))?;
259 let filename = filename.map_err(|e| v2_error(py, e))?;
280 let as_pystring = PyBytes::new(py, filename.as_bytes());
260 let as_pystring = PyBytes::new(py, filename.as_bytes());
281 ret.append(py, as_pystring.into_object());
261 ret.append(py, as_pystring.into_object());
282 }
262 }
283 Ok(ret)
263 Ok(ret)
284 }
264 }
285
265
286 def non_normal_entries_iter(&self) -> PyResult<NonNormalEntriesIterator> {
266 def non_normal_entries_iter(&self) -> PyResult<NonNormalEntriesIterator> {
287 // Make sure the sets are defined before we no longer have a mutable
267 // Make sure the sets are defined before we no longer have a mutable
288 // reference to the dmap.
268 // reference to the dmap.
289 self.inner(py)
269 self.inner(py)
290 .borrow_mut()
270 .borrow_mut()
291 .set_non_normal_other_parent_entries(false);
271 .set_non_normal_other_parent_entries(false);
292
272
293 let leaked_ref = self.inner(py).leak_immutable();
273 let leaked_ref = self.inner(py).leak_immutable();
294
274
295 NonNormalEntriesIterator::from_inner(py, unsafe {
275 NonNormalEntriesIterator::from_inner(py, unsafe {
296 leaked_ref.map(py, |o| {
276 leaked_ref.map(py, |o| {
297 o.iter_non_normal_paths_panic()
277 o.iter_non_normal_paths_panic()
298 })
278 })
299 })
279 })
300 }
280 }
301
281
302 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
282 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
303 let d = d.extract::<PyBytes>(py)?;
283 let d = d.extract::<PyBytes>(py)?;
304 Ok(self.inner(py).borrow_mut()
284 Ok(self.inner(py).borrow_mut()
305 .has_tracked_dir(HgPath::new(d.data(py)))
285 .has_tracked_dir(HgPath::new(d.data(py)))
306 .map_err(|e| {
286 .map_err(|e| {
307 PyErr::new::<exc::ValueError, _>(py, e.to_string())
287 PyErr::new::<exc::ValueError, _>(py, e.to_string())
308 })?
288 })?
309 .to_py_object(py))
289 .to_py_object(py))
310 }
290 }
311
291
312 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
292 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
313 let d = d.extract::<PyBytes>(py)?;
293 let d = d.extract::<PyBytes>(py)?;
314 Ok(self.inner(py).borrow_mut()
294 Ok(self.inner(py).borrow_mut()
315 .has_dir(HgPath::new(d.data(py)))
295 .has_dir(HgPath::new(d.data(py)))
316 .map_err(|e| {
296 .map_err(|e| {
317 PyErr::new::<exc::ValueError, _>(py, e.to_string())
297 PyErr::new::<exc::ValueError, _>(py, e.to_string())
318 })?
298 })?
319 .to_py_object(py))
299 .to_py_object(py))
320 }
300 }
321
301
322 def write_v1(
302 def write_v1(
323 &self,
303 &self,
324 p1: PyObject,
304 p1: PyObject,
325 p2: PyObject,
305 p2: PyObject,
326 now: PyObject
306 now: PyObject
327 ) -> PyResult<PyBytes> {
307 ) -> PyResult<PyBytes> {
328 let now = Timestamp(now.extract(py)?);
308 let now = Timestamp(now.extract(py)?);
329
309
330 let mut inner = self.inner(py).borrow_mut();
310 let mut inner = self.inner(py).borrow_mut();
331 let parents = DirstateParents {
311 let parents = DirstateParents {
332 p1: extract_node_id(py, &p1)?,
312 p1: extract_node_id(py, &p1)?,
333 p2: extract_node_id(py, &p2)?,
313 p2: extract_node_id(py, &p2)?,
334 };
314 };
335 let result = inner.pack_v1(parents, now);
315 let result = inner.pack_v1(parents, now);
336 match result {
316 match result {
337 Ok(packed) => Ok(PyBytes::new(py, &packed)),
317 Ok(packed) => Ok(PyBytes::new(py, &packed)),
338 Err(_) => Err(PyErr::new::<exc::OSError, _>(
318 Err(_) => Err(PyErr::new::<exc::OSError, _>(
339 py,
319 py,
340 "Dirstate error".to_string(),
320 "Dirstate error".to_string(),
341 )),
321 )),
342 }
322 }
343 }
323 }
344
324
345 /// Returns new data together with whether that data should be appended to
325 /// Returns new data together with whether that data should be appended to
346 /// the existing data file whose content is at `self.on_disk` (True),
326 /// the existing data file whose content is at `self.on_disk` (True),
347 /// instead of written to a new data file (False).
327 /// instead of written to a new data file (False).
348 def write_v2(
328 def write_v2(
349 &self,
329 &self,
350 now: PyObject,
330 now: PyObject,
351 can_append: bool,
331 can_append: bool,
352 ) -> PyResult<PyObject> {
332 ) -> PyResult<PyObject> {
353 let now = Timestamp(now.extract(py)?);
333 let now = Timestamp(now.extract(py)?);
354
334
355 let mut inner = self.inner(py).borrow_mut();
335 let mut inner = self.inner(py).borrow_mut();
356 let result = inner.pack_v2(now, can_append);
336 let result = inner.pack_v2(now, can_append);
357 match result {
337 match result {
358 Ok((packed, tree_metadata, append)) => {
338 Ok((packed, tree_metadata, append)) => {
359 let packed = PyBytes::new(py, &packed);
339 let packed = PyBytes::new(py, &packed);
360 let tree_metadata = PyBytes::new(py, &tree_metadata);
340 let tree_metadata = PyBytes::new(py, &tree_metadata);
361 let tuple = (packed, tree_metadata, append);
341 let tuple = (packed, tree_metadata, append);
362 Ok(tuple.to_py_object(py).into_object())
342 Ok(tuple.to_py_object(py).into_object())
363 },
343 },
364 Err(_) => Err(PyErr::new::<exc::OSError, _>(
344 Err(_) => Err(PyErr::new::<exc::OSError, _>(
365 py,
345 py,
366 "Dirstate error".to_string(),
346 "Dirstate error".to_string(),
367 )),
347 )),
368 }
348 }
369 }
349 }
370
350
371 def filefoldmapasdict(&self) -> PyResult<PyDict> {
351 def filefoldmapasdict(&self) -> PyResult<PyDict> {
372 let dict = PyDict::new(py);
352 let dict = PyDict::new(py);
373 for item in self.inner(py).borrow_mut().iter() {
353 for item in self.inner(py).borrow_mut().iter() {
374 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
354 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
375 if entry.state() != EntryState::Removed {
355 if entry.state() != EntryState::Removed {
376 let key = normalize_case(path);
356 let key = normalize_case(path);
377 let value = path;
357 let value = path;
378 dict.set_item(
358 dict.set_item(
379 py,
359 py,
380 PyBytes::new(py, key.as_bytes()).into_object(),
360 PyBytes::new(py, key.as_bytes()).into_object(),
381 PyBytes::new(py, value.as_bytes()).into_object(),
361 PyBytes::new(py, value.as_bytes()).into_object(),
382 )?;
362 )?;
383 }
363 }
384 }
364 }
385 Ok(dict)
365 Ok(dict)
386 }
366 }
387
367
388 def __len__(&self) -> PyResult<usize> {
368 def __len__(&self) -> PyResult<usize> {
389 Ok(self.inner(py).borrow().len())
369 Ok(self.inner(py).borrow().len())
390 }
370 }
391
371
392 def __contains__(&self, key: PyObject) -> PyResult<bool> {
372 def __contains__(&self, key: PyObject) -> PyResult<bool> {
393 let key = key.extract::<PyBytes>(py)?;
373 let key = key.extract::<PyBytes>(py)?;
394 self.inner(py)
374 self.inner(py)
395 .borrow()
375 .borrow()
396 .contains_key(HgPath::new(key.data(py)))
376 .contains_key(HgPath::new(key.data(py)))
397 .map_err(|e| v2_error(py, e))
377 .map_err(|e| v2_error(py, e))
398 }
378 }
399
379
400 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
380 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
401 let key = key.extract::<PyBytes>(py)?;
381 let key = key.extract::<PyBytes>(py)?;
402 let key = HgPath::new(key.data(py));
382 let key = HgPath::new(key.data(py));
403 match self
383 match self
404 .inner(py)
384 .inner(py)
405 .borrow()
385 .borrow()
406 .get(key)
386 .get(key)
407 .map_err(|e| v2_error(py, e))?
387 .map_err(|e| v2_error(py, e))?
408 {
388 {
409 Some(entry) => {
389 Some(entry) => {
410 Ok(DirstateItem::new_as_pyobject(py, entry)?)
390 Ok(DirstateItem::new_as_pyobject(py, entry)?)
411 },
391 },
412 None => Err(PyErr::new::<exc::KeyError, _>(
392 None => Err(PyErr::new::<exc::KeyError, _>(
413 py,
393 py,
414 String::from_utf8_lossy(key.as_bytes()),
394 String::from_utf8_lossy(key.as_bytes()),
415 )),
395 )),
416 }
396 }
417 }
397 }
418
398
419 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
399 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
420 let leaked_ref = self.inner(py).leak_immutable();
400 let leaked_ref = self.inner(py).leak_immutable();
421 DirstateMapKeysIterator::from_inner(
401 DirstateMapKeysIterator::from_inner(
422 py,
402 py,
423 unsafe { leaked_ref.map(py, |o| o.iter()) },
403 unsafe { leaked_ref.map(py, |o| o.iter()) },
424 )
404 )
425 }
405 }
426
406
427 def items(&self) -> PyResult<DirstateMapItemsIterator> {
407 def items(&self) -> PyResult<DirstateMapItemsIterator> {
428 let leaked_ref = self.inner(py).leak_immutable();
408 let leaked_ref = self.inner(py).leak_immutable();
429 DirstateMapItemsIterator::from_inner(
409 DirstateMapItemsIterator::from_inner(
430 py,
410 py,
431 unsafe { leaked_ref.map(py, |o| o.iter()) },
411 unsafe { leaked_ref.map(py, |o| o.iter()) },
432 )
412 )
433 }
413 }
434
414
435 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
415 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
436 let leaked_ref = self.inner(py).leak_immutable();
416 let leaked_ref = self.inner(py).leak_immutable();
437 DirstateMapKeysIterator::from_inner(
417 DirstateMapKeysIterator::from_inner(
438 py,
418 py,
439 unsafe { leaked_ref.map(py, |o| o.iter()) },
419 unsafe { leaked_ref.map(py, |o| o.iter()) },
440 )
420 )
441 }
421 }
442
422
443 // TODO all copymap* methods, see docstring above
423 // TODO all copymap* methods, see docstring above
444 def copymapcopy(&self) -> PyResult<PyDict> {
424 def copymapcopy(&self) -> PyResult<PyDict> {
445 let dict = PyDict::new(py);
425 let dict = PyDict::new(py);
446 for item in self.inner(py).borrow().copy_map_iter() {
426 for item in self.inner(py).borrow().copy_map_iter() {
447 let (key, value) = item.map_err(|e| v2_error(py, e))?;
427 let (key, value) = item.map_err(|e| v2_error(py, e))?;
448 dict.set_item(
428 dict.set_item(
449 py,
429 py,
450 PyBytes::new(py, key.as_bytes()),
430 PyBytes::new(py, key.as_bytes()),
451 PyBytes::new(py, value.as_bytes()),
431 PyBytes::new(py, value.as_bytes()),
452 )?;
432 )?;
453 }
433 }
454 Ok(dict)
434 Ok(dict)
455 }
435 }
456
436
457 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
437 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
458 let key = key.extract::<PyBytes>(py)?;
438 let key = key.extract::<PyBytes>(py)?;
459 match self
439 match self
460 .inner(py)
440 .inner(py)
461 .borrow()
441 .borrow()
462 .copy_map_get(HgPath::new(key.data(py)))
442 .copy_map_get(HgPath::new(key.data(py)))
463 .map_err(|e| v2_error(py, e))?
443 .map_err(|e| v2_error(py, e))?
464 {
444 {
465 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
445 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
466 None => Err(PyErr::new::<exc::KeyError, _>(
446 None => Err(PyErr::new::<exc::KeyError, _>(
467 py,
447 py,
468 String::from_utf8_lossy(key.data(py)),
448 String::from_utf8_lossy(key.data(py)),
469 )),
449 )),
470 }
450 }
471 }
451 }
472 def copymap(&self) -> PyResult<CopyMap> {
452 def copymap(&self) -> PyResult<CopyMap> {
473 CopyMap::from_inner(py, self.clone_ref(py))
453 CopyMap::from_inner(py, self.clone_ref(py))
474 }
454 }
475
455
476 def copymaplen(&self) -> PyResult<usize> {
456 def copymaplen(&self) -> PyResult<usize> {
477 Ok(self.inner(py).borrow().copy_map_len())
457 Ok(self.inner(py).borrow().copy_map_len())
478 }
458 }
479 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
459 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
480 let key = key.extract::<PyBytes>(py)?;
460 let key = key.extract::<PyBytes>(py)?;
481 self.inner(py)
461 self.inner(py)
482 .borrow()
462 .borrow()
483 .copy_map_contains_key(HgPath::new(key.data(py)))
463 .copy_map_contains_key(HgPath::new(key.data(py)))
484 .map_err(|e| v2_error(py, e))
464 .map_err(|e| v2_error(py, e))
485 }
465 }
486 def copymapget(
466 def copymapget(
487 &self,
467 &self,
488 key: PyObject,
468 key: PyObject,
489 default: Option<PyObject>
469 default: Option<PyObject>
490 ) -> PyResult<Option<PyObject>> {
470 ) -> PyResult<Option<PyObject>> {
491 let key = key.extract::<PyBytes>(py)?;
471 let key = key.extract::<PyBytes>(py)?;
492 match self
472 match self
493 .inner(py)
473 .inner(py)
494 .borrow()
474 .borrow()
495 .copy_map_get(HgPath::new(key.data(py)))
475 .copy_map_get(HgPath::new(key.data(py)))
496 .map_err(|e| v2_error(py, e))?
476 .map_err(|e| v2_error(py, e))?
497 {
477 {
498 Some(copy) => Ok(Some(
478 Some(copy) => Ok(Some(
499 PyBytes::new(py, copy.as_bytes()).into_object(),
479 PyBytes::new(py, copy.as_bytes()).into_object(),
500 )),
480 )),
501 None => Ok(default),
481 None => Ok(default),
502 }
482 }
503 }
483 }
504 def copymapsetitem(
484 def copymapsetitem(
505 &self,
485 &self,
506 key: PyObject,
486 key: PyObject,
507 value: PyObject
487 value: PyObject
508 ) -> PyResult<PyObject> {
488 ) -> PyResult<PyObject> {
509 let key = key.extract::<PyBytes>(py)?;
489 let key = key.extract::<PyBytes>(py)?;
510 let value = value.extract::<PyBytes>(py)?;
490 let value = value.extract::<PyBytes>(py)?;
511 self.inner(py)
491 self.inner(py)
512 .borrow_mut()
492 .borrow_mut()
513 .copy_map_insert(
493 .copy_map_insert(
514 HgPathBuf::from_bytes(key.data(py)),
494 HgPathBuf::from_bytes(key.data(py)),
515 HgPathBuf::from_bytes(value.data(py)),
495 HgPathBuf::from_bytes(value.data(py)),
516 )
496 )
517 .map_err(|e| v2_error(py, e))?;
497 .map_err(|e| v2_error(py, e))?;
518 Ok(py.None())
498 Ok(py.None())
519 }
499 }
520 def copymappop(
500 def copymappop(
521 &self,
501 &self,
522 key: PyObject,
502 key: PyObject,
523 default: Option<PyObject>
503 default: Option<PyObject>
524 ) -> PyResult<Option<PyObject>> {
504 ) -> PyResult<Option<PyObject>> {
525 let key = key.extract::<PyBytes>(py)?;
505 let key = key.extract::<PyBytes>(py)?;
526 match self
506 match self
527 .inner(py)
507 .inner(py)
528 .borrow_mut()
508 .borrow_mut()
529 .copy_map_remove(HgPath::new(key.data(py)))
509 .copy_map_remove(HgPath::new(key.data(py)))
530 .map_err(|e| v2_error(py, e))?
510 .map_err(|e| v2_error(py, e))?
531 {
511 {
532 Some(_) => Ok(None),
512 Some(_) => Ok(None),
533 None => Ok(default),
513 None => Ok(default),
534 }
514 }
535 }
515 }
536
516
537 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
517 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
538 let leaked_ref = self.inner(py).leak_immutable();
518 let leaked_ref = self.inner(py).leak_immutable();
539 CopyMapKeysIterator::from_inner(
519 CopyMapKeysIterator::from_inner(
540 py,
520 py,
541 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
521 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
542 )
522 )
543 }
523 }
544
524
545 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
525 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
546 let leaked_ref = self.inner(py).leak_immutable();
526 let leaked_ref = self.inner(py).leak_immutable();
547 CopyMapItemsIterator::from_inner(
527 CopyMapItemsIterator::from_inner(
548 py,
528 py,
549 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
529 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
550 )
530 )
551 }
531 }
552
532
553 def tracked_dirs(&self) -> PyResult<PyList> {
533 def tracked_dirs(&self) -> PyResult<PyList> {
554 let dirs = PyList::new(py, &[]);
534 let dirs = PyList::new(py, &[]);
555 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
535 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
556 .map_err(|e |dirstate_error(py, e))?
536 .map_err(|e |dirstate_error(py, e))?
557 {
537 {
558 let path = path.map_err(|e| v2_error(py, e))?;
538 let path = path.map_err(|e| v2_error(py, e))?;
559 let path = PyBytes::new(py, path.as_bytes());
539 let path = PyBytes::new(py, path.as_bytes());
560 dirs.append(py, path.into_object())
540 dirs.append(py, path.into_object())
561 }
541 }
562 Ok(dirs)
542 Ok(dirs)
563 }
543 }
564
544
565 def debug_iter(&self, all: bool) -> PyResult<PyList> {
545 def debug_iter(&self, all: bool) -> PyResult<PyList> {
566 let dirs = PyList::new(py, &[]);
546 let dirs = PyList::new(py, &[]);
567 for item in self.inner(py).borrow().debug_iter(all) {
547 for item in self.inner(py).borrow().debug_iter(all) {
568 let (path, (state, mode, size, mtime)) =
548 let (path, (state, mode, size, mtime)) =
569 item.map_err(|e| v2_error(py, e))?;
549 item.map_err(|e| v2_error(py, e))?;
570 let path = PyBytes::new(py, path.as_bytes());
550 let path = PyBytes::new(py, path.as_bytes());
571 let item = (path, state, mode, size, mtime);
551 let item = (path, state, mode, size, mtime);
572 dirs.append(py, item.to_py_object(py).into_object())
552 dirs.append(py, item.to_py_object(py).into_object())
573 }
553 }
574 Ok(dirs)
554 Ok(dirs)
575 }
555 }
576 });
556 });
577
557
578 impl DirstateMap {
558 impl DirstateMap {
579 pub fn get_inner_mut<'a>(
559 pub fn get_inner_mut<'a>(
580 &'a self,
560 &'a self,
581 py: Python<'a>,
561 py: Python<'a>,
582 ) -> RefMut<'a, Box<dyn DirstateMapMethods + Send>> {
562 ) -> RefMut<'a, Box<dyn DirstateMapMethods + Send>> {
583 self.inner(py).borrow_mut()
563 self.inner(py).borrow_mut()
584 }
564 }
585 fn translate_key(
565 fn translate_key(
586 py: Python,
566 py: Python,
587 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
567 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
588 ) -> PyResult<Option<PyBytes>> {
568 ) -> PyResult<Option<PyBytes>> {
589 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
569 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
590 Ok(Some(PyBytes::new(py, f.as_bytes())))
570 Ok(Some(PyBytes::new(py, f.as_bytes())))
591 }
571 }
592 fn translate_key_value(
572 fn translate_key_value(
593 py: Python,
573 py: Python,
594 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
574 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
595 ) -> PyResult<Option<(PyBytes, PyObject)>> {
575 ) -> PyResult<Option<(PyBytes, PyObject)>> {
596 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
576 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
597 Ok(Some((
577 Ok(Some((
598 PyBytes::new(py, f.as_bytes()),
578 PyBytes::new(py, f.as_bytes()),
599 DirstateItem::new_as_pyobject(py, entry)?,
579 DirstateItem::new_as_pyobject(py, entry)?,
600 )))
580 )))
601 }
581 }
602 }
582 }
603
583
604 py_shared_iterator!(
584 py_shared_iterator!(
605 DirstateMapKeysIterator,
585 DirstateMapKeysIterator,
606 UnsafePyLeaked<StateMapIter<'static>>,
586 UnsafePyLeaked<StateMapIter<'static>>,
607 DirstateMap::translate_key,
587 DirstateMap::translate_key,
608 Option<PyBytes>
588 Option<PyBytes>
609 );
589 );
610
590
611 py_shared_iterator!(
591 py_shared_iterator!(
612 DirstateMapItemsIterator,
592 DirstateMapItemsIterator,
613 UnsafePyLeaked<StateMapIter<'static>>,
593 UnsafePyLeaked<StateMapIter<'static>>,
614 DirstateMap::translate_key_value,
594 DirstateMap::translate_key_value,
615 Option<(PyBytes, PyObject)>
595 Option<(PyBytes, PyObject)>
616 );
596 );
617
597
618 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
598 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
619 let bytes = obj.extract::<PyBytes>(py)?;
599 let bytes = obj.extract::<PyBytes>(py)?;
620 match bytes.data(py).try_into() {
600 match bytes.data(py).try_into() {
621 Ok(s) => Ok(s),
601 Ok(s) => Ok(s),
622 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
602 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
623 }
603 }
624 }
604 }
625
605
626 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
606 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
627 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
607 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
628 }
608 }
629
609
630 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
610 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
631 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
611 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
632 }
612 }
General Comments 0
You need to be logged in to leave comments. Login now