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