##// END OF EJS Templates
dirstate: Remove return boolean from dirstatemap.dropfile...
Simon Sapin -
r48862:76f1c761 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_dirstate_item(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 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_dirstate_item(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_dirstate_item(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_dirstate_item(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_entry(&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<(), 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(())
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,1308 +1,1307 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_entry(
761 fn set_entry(
762 &mut self,
762 &mut self,
763 filename: &HgPath,
763 filename: &HgPath,
764 entry: DirstateEntry,
764 entry: DirstateEntry,
765 ) -> Result<(), DirstateV2ParseError> {
765 ) -> Result<(), DirstateV2ParseError> {
766 self.get_or_insert(&filename)?.data = NodeData::Entry(entry);
766 self.get_or_insert(&filename)?.data = NodeData::Entry(entry);
767 Ok(())
767 Ok(())
768 }
768 }
769
769
770 fn add_file(
770 fn add_file(
771 &mut self,
771 &mut self,
772 filename: &HgPath,
772 filename: &HgPath,
773 entry: DirstateEntry,
773 entry: DirstateEntry,
774 added: bool,
774 added: bool,
775 merged: bool,
775 merged: bool,
776 from_p2: bool,
776 from_p2: bool,
777 possibly_dirty: bool,
777 possibly_dirty: bool,
778 ) -> Result<(), DirstateError> {
778 ) -> Result<(), DirstateError> {
779 let state;
779 let state;
780 let size;
780 let size;
781 let mtime;
781 let mtime;
782 if added {
782 if added {
783 assert!(!possibly_dirty);
783 assert!(!possibly_dirty);
784 assert!(!from_p2);
784 assert!(!from_p2);
785 state = EntryState::Added;
785 state = EntryState::Added;
786 size = SIZE_NON_NORMAL;
786 size = SIZE_NON_NORMAL;
787 mtime = MTIME_UNSET;
787 mtime = MTIME_UNSET;
788 } else if merged {
788 } else if merged {
789 assert!(!possibly_dirty);
789 assert!(!possibly_dirty);
790 assert!(!from_p2);
790 assert!(!from_p2);
791 state = EntryState::Merged;
791 state = EntryState::Merged;
792 size = SIZE_FROM_OTHER_PARENT;
792 size = SIZE_FROM_OTHER_PARENT;
793 mtime = MTIME_UNSET;
793 mtime = MTIME_UNSET;
794 } else if from_p2 {
794 } else if from_p2 {
795 assert!(!possibly_dirty);
795 assert!(!possibly_dirty);
796 state = EntryState::Normal;
796 state = EntryState::Normal;
797 size = SIZE_FROM_OTHER_PARENT;
797 size = SIZE_FROM_OTHER_PARENT;
798 mtime = MTIME_UNSET;
798 mtime = MTIME_UNSET;
799 } else if possibly_dirty {
799 } else if possibly_dirty {
800 state = EntryState::Normal;
800 state = EntryState::Normal;
801 size = SIZE_NON_NORMAL;
801 size = SIZE_NON_NORMAL;
802 mtime = MTIME_UNSET;
802 mtime = MTIME_UNSET;
803 } else {
803 } else {
804 state = EntryState::Normal;
804 state = EntryState::Normal;
805 size = entry.size() & V1_RANGEMASK;
805 size = entry.size() & V1_RANGEMASK;
806 mtime = entry.mtime() & V1_RANGEMASK;
806 mtime = entry.mtime() & V1_RANGEMASK;
807 }
807 }
808 let mode = entry.mode();
808 let mode = entry.mode();
809 let entry = DirstateEntry::from_v1_data(state, mode, size, mtime);
809 let entry = DirstateEntry::from_v1_data(state, mode, size, mtime);
810
810
811 let old_state = self.get(filename)?.map(|e| e.state());
811 let old_state = self.get(filename)?.map(|e| e.state());
812
812
813 Ok(self.add_or_remove_file(filename, old_state, entry)?)
813 Ok(self.add_or_remove_file(filename, old_state, entry)?)
814 }
814 }
815
815
816 fn remove_file(
816 fn remove_file(
817 &mut self,
817 &mut self,
818 filename: &HgPath,
818 filename: &HgPath,
819 in_merge: bool,
819 in_merge: bool,
820 ) -> Result<(), DirstateError> {
820 ) -> Result<(), DirstateError> {
821 let old_entry_opt = self.get(filename)?;
821 let old_entry_opt = self.get(filename)?;
822 let old_state = old_entry_opt.map(|e| e.state());
822 let old_state = old_entry_opt.map(|e| e.state());
823 let mut size = 0;
823 let mut size = 0;
824 if in_merge {
824 if in_merge {
825 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
825 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
826 // during a merge. So I (marmoute) am not sure we need the
826 // during a merge. So I (marmoute) am not sure we need the
827 // conditionnal at all. Adding double checking this with assert
827 // conditionnal at all. Adding double checking this with assert
828 // would be nice.
828 // would be nice.
829 if let Some(old_entry) = old_entry_opt {
829 if let Some(old_entry) = old_entry_opt {
830 // backup the previous state
830 // backup the previous state
831 if old_entry.state() == EntryState::Merged {
831 if old_entry.state() == EntryState::Merged {
832 size = SIZE_NON_NORMAL;
832 size = SIZE_NON_NORMAL;
833 } else if old_entry.state() == EntryState::Normal
833 } else if old_entry.state() == EntryState::Normal
834 && old_entry.size() == SIZE_FROM_OTHER_PARENT
834 && old_entry.size() == SIZE_FROM_OTHER_PARENT
835 {
835 {
836 // other parent
836 // other parent
837 size = SIZE_FROM_OTHER_PARENT;
837 size = SIZE_FROM_OTHER_PARENT;
838 }
838 }
839 }
839 }
840 }
840 }
841 if size == 0 {
841 if size == 0 {
842 self.copy_map_remove(filename)?;
842 self.copy_map_remove(filename)?;
843 }
843 }
844 let entry = DirstateEntry::new_removed(size);
844 let entry = DirstateEntry::new_removed(size);
845 Ok(self.add_or_remove_file(filename, old_state, entry)?)
845 Ok(self.add_or_remove_file(filename, old_state, entry)?)
846 }
846 }
847
847
848 fn drop_file(&mut self, filename: &HgPath) -> Result<bool, DirstateError> {
848 fn drop_file(&mut self, filename: &HgPath) -> Result<(), DirstateError> {
849 let was_tracked = self
849 let was_tracked = self
850 .get(filename)?
850 .get(filename)?
851 .map_or(false, |e| e.state().is_tracked());
851 .map_or(false, |e| e.state().is_tracked());
852 struct Dropped {
852 struct Dropped {
853 was_tracked: bool,
853 was_tracked: bool,
854 had_entry: bool,
854 had_entry: bool,
855 had_copy_source: bool,
855 had_copy_source: bool,
856 }
856 }
857
857
858 /// If this returns `Ok(Some((dropped, removed)))`, then
858 /// If this returns `Ok(Some((dropped, removed)))`, then
859 ///
859 ///
860 /// * `dropped` is about the leaf node that was at `filename`
860 /// * `dropped` is about the leaf node that was at `filename`
861 /// * `removed` is whether this particular level of recursion just
861 /// * `removed` is whether this particular level of recursion just
862 /// removed a node in `nodes`.
862 /// removed a node in `nodes`.
863 fn recur<'on_disk>(
863 fn recur<'on_disk>(
864 on_disk: &'on_disk [u8],
864 on_disk: &'on_disk [u8],
865 unreachable_bytes: &mut u32,
865 unreachable_bytes: &mut u32,
866 nodes: &mut ChildNodes<'on_disk>,
866 nodes: &mut ChildNodes<'on_disk>,
867 path: &HgPath,
867 path: &HgPath,
868 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
868 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
869 let (first_path_component, rest_of_path) =
869 let (first_path_component, rest_of_path) =
870 path.split_first_component();
870 path.split_first_component();
871 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
871 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
872 let node = if let Some(node) = nodes.get_mut(first_path_component)
872 let node = if let Some(node) = nodes.get_mut(first_path_component)
873 {
873 {
874 node
874 node
875 } else {
875 } else {
876 return Ok(None);
876 return Ok(None);
877 };
877 };
878 let dropped;
878 let dropped;
879 if let Some(rest) = rest_of_path {
879 if let Some(rest) = rest_of_path {
880 if let Some((d, removed)) = recur(
880 if let Some((d, removed)) = recur(
881 on_disk,
881 on_disk,
882 unreachable_bytes,
882 unreachable_bytes,
883 &mut node.children,
883 &mut node.children,
884 rest,
884 rest,
885 )? {
885 )? {
886 dropped = d;
886 dropped = d;
887 if dropped.had_entry {
887 if dropped.had_entry {
888 node.descendants_with_entry_count -= 1;
888 node.descendants_with_entry_count -= 1;
889 }
889 }
890 if dropped.was_tracked {
890 if dropped.was_tracked {
891 node.tracked_descendants_count -= 1;
891 node.tracked_descendants_count -= 1;
892 }
892 }
893
893
894 // Directory caches must be invalidated when removing a
894 // Directory caches must be invalidated when removing a
895 // child node
895 // child node
896 if removed {
896 if removed {
897 if let NodeData::CachedDirectory { .. } = &node.data {
897 if let NodeData::CachedDirectory { .. } = &node.data {
898 node.data = NodeData::None
898 node.data = NodeData::None
899 }
899 }
900 }
900 }
901 } else {
901 } else {
902 return Ok(None);
902 return Ok(None);
903 }
903 }
904 } else {
904 } else {
905 let had_entry = node.data.has_entry();
905 let had_entry = node.data.has_entry();
906 if had_entry {
906 if had_entry {
907 node.data = NodeData::None
907 node.data = NodeData::None
908 }
908 }
909 if let Some(source) = &node.copy_source {
909 if let Some(source) = &node.copy_source {
910 DirstateMap::count_dropped_path(unreachable_bytes, source)
910 DirstateMap::count_dropped_path(unreachable_bytes, source)
911 }
911 }
912 dropped = Dropped {
912 dropped = Dropped {
913 was_tracked: node
913 was_tracked: node
914 .data
914 .data
915 .as_entry()
915 .as_entry()
916 .map_or(false, |entry| entry.state().is_tracked()),
916 .map_or(false, |entry| entry.state().is_tracked()),
917 had_entry,
917 had_entry,
918 had_copy_source: node.copy_source.take().is_some(),
918 had_copy_source: node.copy_source.take().is_some(),
919 };
919 };
920 }
920 }
921 // After recursion, for both leaf (rest_of_path is None) nodes and
921 // After recursion, for both leaf (rest_of_path is None) nodes and
922 // parent nodes, remove a node if it just became empty.
922 // parent nodes, remove a node if it just became empty.
923 let remove = !node.data.has_entry()
923 let remove = !node.data.has_entry()
924 && node.copy_source.is_none()
924 && node.copy_source.is_none()
925 && node.children.is_empty();
925 && node.children.is_empty();
926 if remove {
926 if remove {
927 let (key, _) =
927 let (key, _) =
928 nodes.remove_entry(first_path_component).unwrap();
928 nodes.remove_entry(first_path_component).unwrap();
929 DirstateMap::count_dropped_path(
929 DirstateMap::count_dropped_path(
930 unreachable_bytes,
930 unreachable_bytes,
931 key.full_path(),
931 key.full_path(),
932 )
932 )
933 }
933 }
934 Ok(Some((dropped, remove)))
934 Ok(Some((dropped, remove)))
935 }
935 }
936
936
937 if let Some((dropped, _removed)) = recur(
937 if let Some((dropped, _removed)) = recur(
938 self.on_disk,
938 self.on_disk,
939 &mut self.unreachable_bytes,
939 &mut self.unreachable_bytes,
940 &mut self.root,
940 &mut self.root,
941 filename,
941 filename,
942 )? {
942 )? {
943 if dropped.had_entry {
943 if dropped.had_entry {
944 self.nodes_with_entry_count -= 1
944 self.nodes_with_entry_count -= 1
945 }
945 }
946 if dropped.had_copy_source {
946 if dropped.had_copy_source {
947 self.nodes_with_copy_source_count -= 1
947 self.nodes_with_copy_source_count -= 1
948 }
948 }
949 Ok(dropped.had_entry)
950 } else {
949 } else {
951 debug_assert!(!was_tracked);
950 debug_assert!(!was_tracked);
952 Ok(false)
953 }
951 }
952 Ok(())
954 }
953 }
955
954
956 fn clear_ambiguous_times(
955 fn clear_ambiguous_times(
957 &mut self,
956 &mut self,
958 filenames: Vec<HgPathBuf>,
957 filenames: Vec<HgPathBuf>,
959 now: i32,
958 now: i32,
960 ) -> Result<(), DirstateV2ParseError> {
959 ) -> Result<(), DirstateV2ParseError> {
961 for filename in filenames {
960 for filename in filenames {
962 if let Some(node) = Self::get_node_mut(
961 if let Some(node) = Self::get_node_mut(
963 self.on_disk,
962 self.on_disk,
964 &mut self.unreachable_bytes,
963 &mut self.unreachable_bytes,
965 &mut self.root,
964 &mut self.root,
966 &filename,
965 &filename,
967 )? {
966 )? {
968 if let NodeData::Entry(entry) = &mut node.data {
967 if let NodeData::Entry(entry) = &mut node.data {
969 entry.clear_ambiguous_mtime(now);
968 entry.clear_ambiguous_mtime(now);
970 }
969 }
971 }
970 }
972 }
971 }
973 Ok(())
972 Ok(())
974 }
973 }
975
974
976 fn non_normal_entries_contains(
975 fn non_normal_entries_contains(
977 &mut self,
976 &mut self,
978 key: &HgPath,
977 key: &HgPath,
979 ) -> Result<bool, DirstateV2ParseError> {
978 ) -> Result<bool, DirstateV2ParseError> {
980 Ok(if let Some(node) = self.get_node(key)? {
979 Ok(if let Some(node) = self.get_node(key)? {
981 node.entry()?.map_or(false, |entry| entry.is_non_normal())
980 node.entry()?.map_or(false, |entry| entry.is_non_normal())
982 } else {
981 } else {
983 false
982 false
984 })
983 })
985 }
984 }
986
985
987 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool {
986 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool {
988 // Do nothing, this `DirstateMap` does not have a separate "non normal
987 // Do nothing, this `DirstateMap` does not have a separate "non normal
989 // entries" set that need to be kept up to date.
988 // entries" set that need to be kept up to date.
990 if let Ok(Some(v)) = self.get(key) {
989 if let Ok(Some(v)) = self.get(key) {
991 return v.is_non_normal();
990 return v.is_non_normal();
992 }
991 }
993 false
992 false
994 }
993 }
995
994
996 fn non_normal_entries_add(&mut self, _key: &HgPath) {
995 fn non_normal_entries_add(&mut self, _key: &HgPath) {
997 // Do nothing, this `DirstateMap` does not have a separate "non normal
996 // Do nothing, this `DirstateMap` does not have a separate "non normal
998 // entries" set that need to be kept up to date
997 // entries" set that need to be kept up to date
999 }
998 }
1000
999
1001 fn non_normal_or_other_parent_paths(
1000 fn non_normal_or_other_parent_paths(
1002 &mut self,
1001 &mut self,
1003 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
1002 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
1004 {
1003 {
1005 Box::new(self.filter_full_paths(|entry| {
1004 Box::new(self.filter_full_paths(|entry| {
1006 entry.is_non_normal() || entry.is_from_other_parent()
1005 entry.is_non_normal() || entry.is_from_other_parent()
1007 }))
1006 }))
1008 }
1007 }
1009
1008
1010 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
1009 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
1011 // Do nothing, this `DirstateMap` does not have a separate "non normal
1010 // Do nothing, this `DirstateMap` does not have a separate "non normal
1012 // entries" and "from other parent" sets that need to be recomputed
1011 // entries" and "from other parent" sets that need to be recomputed
1013 }
1012 }
1014
1013
1015 fn iter_non_normal_paths(
1014 fn iter_non_normal_paths(
1016 &mut self,
1015 &mut self,
1017 ) -> Box<
1016 ) -> Box<
1018 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
1017 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
1019 > {
1018 > {
1020 self.iter_non_normal_paths_panic()
1019 self.iter_non_normal_paths_panic()
1021 }
1020 }
1022
1021
1023 fn iter_non_normal_paths_panic(
1022 fn iter_non_normal_paths_panic(
1024 &self,
1023 &self,
1025 ) -> Box<
1024 ) -> Box<
1026 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
1025 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
1027 > {
1026 > {
1028 Box::new(self.filter_full_paths(|entry| entry.is_non_normal()))
1027 Box::new(self.filter_full_paths(|entry| entry.is_non_normal()))
1029 }
1028 }
1030
1029
1031 fn iter_other_parent_paths(
1030 fn iter_other_parent_paths(
1032 &mut self,
1031 &mut self,
1033 ) -> Box<
1032 ) -> Box<
1034 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
1033 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
1035 > {
1034 > {
1036 Box::new(self.filter_full_paths(|entry| entry.is_from_other_parent()))
1035 Box::new(self.filter_full_paths(|entry| entry.is_from_other_parent()))
1037 }
1036 }
1038
1037
1039 fn has_tracked_dir(
1038 fn has_tracked_dir(
1040 &mut self,
1039 &mut self,
1041 directory: &HgPath,
1040 directory: &HgPath,
1042 ) -> Result<bool, DirstateError> {
1041 ) -> Result<bool, DirstateError> {
1043 if let Some(node) = self.get_node(directory)? {
1042 if let Some(node) = self.get_node(directory)? {
1044 // A node without a `DirstateEntry` was created to hold child
1043 // A node without a `DirstateEntry` was created to hold child
1045 // nodes, and is therefore a directory.
1044 // nodes, and is therefore a directory.
1046 let state = node.state()?;
1045 let state = node.state()?;
1047 Ok(state.is_none() && node.tracked_descendants_count() > 0)
1046 Ok(state.is_none() && node.tracked_descendants_count() > 0)
1048 } else {
1047 } else {
1049 Ok(false)
1048 Ok(false)
1050 }
1049 }
1051 }
1050 }
1052
1051
1053 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
1052 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
1054 if let Some(node) = self.get_node(directory)? {
1053 if let Some(node) = self.get_node(directory)? {
1055 // A node without a `DirstateEntry` was created to hold child
1054 // A node without a `DirstateEntry` was created to hold child
1056 // nodes, and is therefore a directory.
1055 // nodes, and is therefore a directory.
1057 let state = node.state()?;
1056 let state = node.state()?;
1058 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
1057 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
1059 } else {
1058 } else {
1060 Ok(false)
1059 Ok(false)
1061 }
1060 }
1062 }
1061 }
1063
1062
1064 #[timed]
1063 #[timed]
1065 fn pack_v1(
1064 fn pack_v1(
1066 &mut self,
1065 &mut self,
1067 parents: DirstateParents,
1066 parents: DirstateParents,
1068 now: Timestamp,
1067 now: Timestamp,
1069 ) -> Result<Vec<u8>, DirstateError> {
1068 ) -> Result<Vec<u8>, DirstateError> {
1070 let now: i32 = now.0.try_into().expect("time overflow");
1069 let now: i32 = now.0.try_into().expect("time overflow");
1071 let mut ambiguous_mtimes = Vec::new();
1070 let mut ambiguous_mtimes = Vec::new();
1072 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1071 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1073 // reallocations
1072 // reallocations
1074 let mut size = parents.as_bytes().len();
1073 let mut size = parents.as_bytes().len();
1075 for node in self.iter_nodes() {
1074 for node in self.iter_nodes() {
1076 let node = node?;
1075 let node = node?;
1077 if let Some(entry) = node.entry()? {
1076 if let Some(entry) = node.entry()? {
1078 size += packed_entry_size(
1077 size += packed_entry_size(
1079 node.full_path(self.on_disk)?,
1078 node.full_path(self.on_disk)?,
1080 node.copy_source(self.on_disk)?,
1079 node.copy_source(self.on_disk)?,
1081 );
1080 );
1082 if entry.mtime_is_ambiguous(now) {
1081 if entry.mtime_is_ambiguous(now) {
1083 ambiguous_mtimes.push(
1082 ambiguous_mtimes.push(
1084 node.full_path_borrowed(self.on_disk)?
1083 node.full_path_borrowed(self.on_disk)?
1085 .detach_from_tree(),
1084 .detach_from_tree(),
1086 )
1085 )
1087 }
1086 }
1088 }
1087 }
1089 }
1088 }
1090 self.clear_known_ambiguous_mtimes(&ambiguous_mtimes)?;
1089 self.clear_known_ambiguous_mtimes(&ambiguous_mtimes)?;
1091
1090
1092 let mut packed = Vec::with_capacity(size);
1091 let mut packed = Vec::with_capacity(size);
1093 packed.extend(parents.as_bytes());
1092 packed.extend(parents.as_bytes());
1094
1093
1095 for node in self.iter_nodes() {
1094 for node in self.iter_nodes() {
1096 let node = node?;
1095 let node = node?;
1097 if let Some(entry) = node.entry()? {
1096 if let Some(entry) = node.entry()? {
1098 pack_entry(
1097 pack_entry(
1099 node.full_path(self.on_disk)?,
1098 node.full_path(self.on_disk)?,
1100 &entry,
1099 &entry,
1101 node.copy_source(self.on_disk)?,
1100 node.copy_source(self.on_disk)?,
1102 &mut packed,
1101 &mut packed,
1103 );
1102 );
1104 }
1103 }
1105 }
1104 }
1106 Ok(packed)
1105 Ok(packed)
1107 }
1106 }
1108
1107
1109 /// Returns new data and metadata together with whether that data should be
1108 /// Returns new data and metadata together with whether that data should be
1110 /// appended to the existing data file whose content is at
1109 /// appended to the existing data file whose content is at
1111 /// `self.on_disk` (true), instead of written to a new data file
1110 /// `self.on_disk` (true), instead of written to a new data file
1112 /// (false).
1111 /// (false).
1113 #[timed]
1112 #[timed]
1114 fn pack_v2(
1113 fn pack_v2(
1115 &mut self,
1114 &mut self,
1116 now: Timestamp,
1115 now: Timestamp,
1117 can_append: bool,
1116 can_append: bool,
1118 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
1117 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
1119 // TODO:Β how do we want to handle this in 2038?
1118 // TODO:Β how do we want to handle this in 2038?
1120 let now: i32 = now.0.try_into().expect("time overflow");
1119 let now: i32 = now.0.try_into().expect("time overflow");
1121 let mut paths = Vec::new();
1120 let mut paths = Vec::new();
1122 for node in self.iter_nodes() {
1121 for node in self.iter_nodes() {
1123 let node = node?;
1122 let node = node?;
1124 if let Some(entry) = node.entry()? {
1123 if let Some(entry) = node.entry()? {
1125 if entry.mtime_is_ambiguous(now) {
1124 if entry.mtime_is_ambiguous(now) {
1126 paths.push(
1125 paths.push(
1127 node.full_path_borrowed(self.on_disk)?
1126 node.full_path_borrowed(self.on_disk)?
1128 .detach_from_tree(),
1127 .detach_from_tree(),
1129 )
1128 )
1130 }
1129 }
1131 }
1130 }
1132 }
1131 }
1133 // Borrow of `self` ends here since we collect cloned paths
1132 // Borrow of `self` ends here since we collect cloned paths
1134
1133
1135 self.clear_known_ambiguous_mtimes(&paths)?;
1134 self.clear_known_ambiguous_mtimes(&paths)?;
1136
1135
1137 on_disk::write(self, can_append)
1136 on_disk::write(self, can_append)
1138 }
1137 }
1139
1138
1140 fn status<'a>(
1139 fn status<'a>(
1141 &'a mut self,
1140 &'a mut self,
1142 matcher: &'a (dyn Matcher + Sync),
1141 matcher: &'a (dyn Matcher + Sync),
1143 root_dir: PathBuf,
1142 root_dir: PathBuf,
1144 ignore_files: Vec<PathBuf>,
1143 ignore_files: Vec<PathBuf>,
1145 options: StatusOptions,
1144 options: StatusOptions,
1146 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
1145 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
1147 {
1146 {
1148 super::status::status(self, matcher, root_dir, ignore_files, options)
1147 super::status::status(self, matcher, root_dir, ignore_files, options)
1149 }
1148 }
1150
1149
1151 fn copy_map_len(&self) -> usize {
1150 fn copy_map_len(&self) -> usize {
1152 self.nodes_with_copy_source_count as usize
1151 self.nodes_with_copy_source_count as usize
1153 }
1152 }
1154
1153
1155 fn copy_map_iter(&self) -> CopyMapIter<'_> {
1154 fn copy_map_iter(&self) -> CopyMapIter<'_> {
1156 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1155 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1157 Ok(if let Some(source) = node.copy_source(self.on_disk)? {
1156 Ok(if let Some(source) = node.copy_source(self.on_disk)? {
1158 Some((node.full_path(self.on_disk)?, source))
1157 Some((node.full_path(self.on_disk)?, source))
1159 } else {
1158 } else {
1160 None
1159 None
1161 })
1160 })
1162 }))
1161 }))
1163 }
1162 }
1164
1163
1165 fn copy_map_contains_key(
1164 fn copy_map_contains_key(
1166 &self,
1165 &self,
1167 key: &HgPath,
1166 key: &HgPath,
1168 ) -> Result<bool, DirstateV2ParseError> {
1167 ) -> Result<bool, DirstateV2ParseError> {
1169 Ok(if let Some(node) = self.get_node(key)? {
1168 Ok(if let Some(node) = self.get_node(key)? {
1170 node.has_copy_source()
1169 node.has_copy_source()
1171 } else {
1170 } else {
1172 false
1171 false
1173 })
1172 })
1174 }
1173 }
1175
1174
1176 fn copy_map_get(
1175 fn copy_map_get(
1177 &self,
1176 &self,
1178 key: &HgPath,
1177 key: &HgPath,
1179 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1178 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1180 if let Some(node) = self.get_node(key)? {
1179 if let Some(node) = self.get_node(key)? {
1181 if let Some(source) = node.copy_source(self.on_disk)? {
1180 if let Some(source) = node.copy_source(self.on_disk)? {
1182 return Ok(Some(source));
1181 return Ok(Some(source));
1183 }
1182 }
1184 }
1183 }
1185 Ok(None)
1184 Ok(None)
1186 }
1185 }
1187
1186
1188 fn copy_map_remove(
1187 fn copy_map_remove(
1189 &mut self,
1188 &mut self,
1190 key: &HgPath,
1189 key: &HgPath,
1191 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1190 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1192 let count = &mut self.nodes_with_copy_source_count;
1191 let count = &mut self.nodes_with_copy_source_count;
1193 let unreachable_bytes = &mut self.unreachable_bytes;
1192 let unreachable_bytes = &mut self.unreachable_bytes;
1194 Ok(Self::get_node_mut(
1193 Ok(Self::get_node_mut(
1195 self.on_disk,
1194 self.on_disk,
1196 unreachable_bytes,
1195 unreachable_bytes,
1197 &mut self.root,
1196 &mut self.root,
1198 key,
1197 key,
1199 )?
1198 )?
1200 .and_then(|node| {
1199 .and_then(|node| {
1201 if let Some(source) = &node.copy_source {
1200 if let Some(source) = &node.copy_source {
1202 *count -= 1;
1201 *count -= 1;
1203 Self::count_dropped_path(unreachable_bytes, source);
1202 Self::count_dropped_path(unreachable_bytes, source);
1204 }
1203 }
1205 node.copy_source.take().map(Cow::into_owned)
1204 node.copy_source.take().map(Cow::into_owned)
1206 }))
1205 }))
1207 }
1206 }
1208
1207
1209 fn copy_map_insert(
1208 fn copy_map_insert(
1210 &mut self,
1209 &mut self,
1211 key: HgPathBuf,
1210 key: HgPathBuf,
1212 value: HgPathBuf,
1211 value: HgPathBuf,
1213 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1212 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1214 let node = Self::get_or_insert_node(
1213 let node = Self::get_or_insert_node(
1215 self.on_disk,
1214 self.on_disk,
1216 &mut self.unreachable_bytes,
1215 &mut self.unreachable_bytes,
1217 &mut self.root,
1216 &mut self.root,
1218 &key,
1217 &key,
1219 WithBasename::to_cow_owned,
1218 WithBasename::to_cow_owned,
1220 |_ancestor| {},
1219 |_ancestor| {},
1221 )?;
1220 )?;
1222 if node.copy_source.is_none() {
1221 if node.copy_source.is_none() {
1223 self.nodes_with_copy_source_count += 1
1222 self.nodes_with_copy_source_count += 1
1224 }
1223 }
1225 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1224 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1226 }
1225 }
1227
1226
1228 fn len(&self) -> usize {
1227 fn len(&self) -> usize {
1229 self.nodes_with_entry_count as usize
1228 self.nodes_with_entry_count as usize
1230 }
1229 }
1231
1230
1232 fn contains_key(
1231 fn contains_key(
1233 &self,
1232 &self,
1234 key: &HgPath,
1233 key: &HgPath,
1235 ) -> Result<bool, DirstateV2ParseError> {
1234 ) -> Result<bool, DirstateV2ParseError> {
1236 Ok(self.get(key)?.is_some())
1235 Ok(self.get(key)?.is_some())
1237 }
1236 }
1238
1237
1239 fn get(
1238 fn get(
1240 &self,
1239 &self,
1241 key: &HgPath,
1240 key: &HgPath,
1242 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1241 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1243 Ok(if let Some(node) = self.get_node(key)? {
1242 Ok(if let Some(node) = self.get_node(key)? {
1244 node.entry()?
1243 node.entry()?
1245 } else {
1244 } else {
1246 None
1245 None
1247 })
1246 })
1248 }
1247 }
1249
1248
1250 fn iter(&self) -> StateMapIter<'_> {
1249 fn iter(&self) -> StateMapIter<'_> {
1251 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1250 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1252 Ok(if let Some(entry) = node.entry()? {
1251 Ok(if let Some(entry) = node.entry()? {
1253 Some((node.full_path(self.on_disk)?, entry))
1252 Some((node.full_path(self.on_disk)?, entry))
1254 } else {
1253 } else {
1255 None
1254 None
1256 })
1255 })
1257 }))
1256 }))
1258 }
1257 }
1259
1258
1260 fn iter_tracked_dirs(
1259 fn iter_tracked_dirs(
1261 &mut self,
1260 &mut self,
1262 ) -> Result<
1261 ) -> Result<
1263 Box<
1262 Box<
1264 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1263 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1265 + Send
1264 + Send
1266 + '_,
1265 + '_,
1267 >,
1266 >,
1268 DirstateError,
1267 DirstateError,
1269 > {
1268 > {
1270 let on_disk = self.on_disk;
1269 let on_disk = self.on_disk;
1271 Ok(Box::new(filter_map_results(
1270 Ok(Box::new(filter_map_results(
1272 self.iter_nodes(),
1271 self.iter_nodes(),
1273 move |node| {
1272 move |node| {
1274 Ok(if node.tracked_descendants_count() > 0 {
1273 Ok(if node.tracked_descendants_count() > 0 {
1275 Some(node.full_path(on_disk)?)
1274 Some(node.full_path(on_disk)?)
1276 } else {
1275 } else {
1277 None
1276 None
1278 })
1277 })
1279 },
1278 },
1280 )))
1279 )))
1281 }
1280 }
1282
1281
1283 fn debug_iter(
1282 fn debug_iter(
1284 &self,
1283 &self,
1285 all: bool,
1284 all: bool,
1286 ) -> Box<
1285 ) -> Box<
1287 dyn Iterator<
1286 dyn Iterator<
1288 Item = Result<
1287 Item = Result<
1289 (&HgPath, (u8, i32, i32, i32)),
1288 (&HgPath, (u8, i32, i32, i32)),
1290 DirstateV2ParseError,
1289 DirstateV2ParseError,
1291 >,
1290 >,
1292 > + Send
1291 > + Send
1293 + '_,
1292 + '_,
1294 > {
1293 > {
1295 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1294 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1296 let debug_tuple = if let Some(entry) = node.entry()? {
1295 let debug_tuple = if let Some(entry) = node.entry()? {
1297 entry.debug_tuple()
1296 entry.debug_tuple()
1298 } else if !all {
1297 } else if !all {
1299 return Ok(None);
1298 return Ok(None);
1300 } else if let Some(mtime) = node.cached_directory_mtime() {
1299 } else if let Some(mtime) = node.cached_directory_mtime() {
1301 (b' ', 0, -1, mtime.seconds() as i32)
1300 (b' ', 0, -1, mtime.seconds() as i32)
1302 } else {
1301 } else {
1303 (b' ', 0, -1, -1)
1302 (b' ', 0, -1, -1)
1304 };
1303 };
1305 Ok(Some((node.full_path(self.on_disk)?, debug_tuple)))
1304 Ok(Some((node.full_path(self.on_disk)?, debug_tuple)))
1306 }))
1305 }))
1307 }
1306 }
1308 }
1307 }
@@ -1,573 +1,573 b''
1 use std::path::PathBuf;
1 use std::path::PathBuf;
2
2
3 use crate::dirstate::parsers::Timestamp;
3 use crate::dirstate::parsers::Timestamp;
4 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
4 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
5 use crate::matchers::Matcher;
5 use crate::matchers::Matcher;
6 use crate::utils::hg_path::{HgPath, HgPathBuf};
6 use crate::utils::hg_path::{HgPath, HgPathBuf};
7 use crate::CopyMapIter;
7 use crate::CopyMapIter;
8 use crate::DirstateEntry;
8 use crate::DirstateEntry;
9 use crate::DirstateError;
9 use crate::DirstateError;
10 use crate::DirstateMap;
10 use crate::DirstateMap;
11 use crate::DirstateParents;
11 use crate::DirstateParents;
12 use crate::DirstateStatus;
12 use crate::DirstateStatus;
13 use crate::PatternFileWarning;
13 use crate::PatternFileWarning;
14 use crate::StateMapIter;
14 use crate::StateMapIter;
15 use crate::StatusError;
15 use crate::StatusError;
16 use crate::StatusOptions;
16 use crate::StatusOptions;
17
17
18 /// `rust/hg-cpython/src/dirstate/dirstate_map.rs` implements in Rust a
18 /// `rust/hg-cpython/src/dirstate/dirstate_map.rs` implements in Rust a
19 /// `DirstateMap` Python class that wraps `Box<dyn DirstateMapMethods + Send>`,
19 /// `DirstateMap` Python class that wraps `Box<dyn DirstateMapMethods + Send>`,
20 /// a trait object of this trait. Except for constructors, this trait defines
20 /// a trait object of this trait. Except for constructors, this trait defines
21 /// all APIs that the class needs to interact with its inner dirstate map.
21 /// all APIs that the class needs to interact with its inner dirstate map.
22 ///
22 ///
23 /// A trait object is used to support two different concrete types:
23 /// A trait object is used to support two different concrete types:
24 ///
24 ///
25 /// * `rust/hg-core/src/dirstate/dirstate_map.rs` defines the "flat dirstate
25 /// * `rust/hg-core/src/dirstate/dirstate_map.rs` defines the "flat dirstate
26 /// map" which is based on a few large `HgPath`-keyed `HashMap` and `HashSet`
26 /// map" which is based on a few large `HgPath`-keyed `HashMap` and `HashSet`
27 /// fields.
27 /// fields.
28 /// * `rust/hg-core/src/dirstate_tree/dirstate_map.rs` defines the "tree
28 /// * `rust/hg-core/src/dirstate_tree/dirstate_map.rs` defines the "tree
29 /// dirstate map" based on a tree data struture with nodes for directories
29 /// dirstate map" based on a tree data struture with nodes for directories
30 /// containing child nodes for their files and sub-directories. This tree
30 /// containing child nodes for their files and sub-directories. This tree
31 /// enables a more efficient algorithm for `hg status`, but its details are
31 /// enables a more efficient algorithm for `hg status`, but its details are
32 /// abstracted in this trait.
32 /// abstracted in this trait.
33 ///
33 ///
34 /// The dirstate map associates paths of files in the working directory to
34 /// The dirstate map associates paths of files in the working directory to
35 /// various information about the state of those files.
35 /// various information about the state of those files.
36 pub trait DirstateMapMethods {
36 pub trait DirstateMapMethods {
37 /// Remove information about all files in this map
37 /// Remove information about all files in this map
38 fn clear(&mut self);
38 fn clear(&mut self);
39
39
40 /// Add the given filename to the map if it is not already there, and
40 /// Add the given filename to the map if it is not already there, and
41 /// associate the given entry with it.
41 /// associate the given entry with it.
42 fn set_entry(
42 fn set_entry(
43 &mut self,
43 &mut self,
44 filename: &HgPath,
44 filename: &HgPath,
45 entry: DirstateEntry,
45 entry: DirstateEntry,
46 ) -> Result<(), DirstateV2ParseError>;
46 ) -> Result<(), DirstateV2ParseError>;
47
47
48 /// Add or change the information associated to a given file.
48 /// Add or change the information associated to a given file.
49 ///
49 ///
50 /// `old_state` is the state in the entry that `get` would have returned
50 /// `old_state` is the state in the entry that `get` would have returned
51 /// before this call, or `EntryState::Unknown` if there was no such entry.
51 /// before this call, or `EntryState::Unknown` if there was no such entry.
52 ///
52 ///
53 /// `entry.state` should never be `EntryState::Unknown`.
53 /// `entry.state` should never be `EntryState::Unknown`.
54 fn add_file(
54 fn add_file(
55 &mut self,
55 &mut self,
56 filename: &HgPath,
56 filename: &HgPath,
57 entry: DirstateEntry,
57 entry: DirstateEntry,
58 added: bool,
58 added: bool,
59 merged: bool,
59 merged: bool,
60 from_p2: bool,
60 from_p2: bool,
61 possibly_dirty: bool,
61 possibly_dirty: bool,
62 ) -> Result<(), DirstateError>;
62 ) -> Result<(), DirstateError>;
63
63
64 /// Mark a file as "removed" (as in `hg rm`).
64 /// Mark a file as "removed" (as in `hg rm`).
65 ///
65 ///
66 /// `old_state` is the state in the entry that `get` would have returned
66 /// `old_state` is the state in the entry that `get` would have returned
67 /// before this call, or `EntryState::Unknown` if there was no such entry.
67 /// before this call, or `EntryState::Unknown` if there was no such entry.
68 ///
68 ///
69 /// `size` is not actually a size but the 0 or -1 or -2 value that would be
69 /// `size` is not actually a size but the 0 or -1 or -2 value that would be
70 /// put in the size field in the dirstate-v1Β format.
70 /// put in the size field in the dirstate-v1Β format.
71 fn remove_file(
71 fn remove_file(
72 &mut self,
72 &mut self,
73 filename: &HgPath,
73 filename: &HgPath,
74 in_merge: bool,
74 in_merge: bool,
75 ) -> Result<(), DirstateError>;
75 ) -> Result<(), DirstateError>;
76
76
77 /// Drop information about this file from the map if any, and return
77 /// Drop information about this file from the map if any, and return
78 /// whether there was any.
78 /// whether there was any.
79 ///
79 ///
80 /// `get` will now return `None` for this filename.
80 /// `get` will now return `None` for this filename.
81 ///
81 ///
82 /// `old_state` is the state in the entry that `get` would have returned
82 /// `old_state` is the state in the entry that `get` would have returned
83 /// before this call, or `EntryState::Unknown` if there was no such entry.
83 /// before this call, or `EntryState::Unknown` if there was no such entry.
84 fn drop_file(&mut self, filename: &HgPath) -> Result<bool, DirstateError>;
84 fn drop_file(&mut self, filename: &HgPath) -> Result<(), DirstateError>;
85
85
86 /// Among given files, mark the stored `mtime` as ambiguous if there is one
86 /// Among given files, mark the stored `mtime` as ambiguous if there is one
87 /// (if `state == EntryState::Normal`) equal to the given current Unix
87 /// (if `state == EntryState::Normal`) equal to the given current Unix
88 /// timestamp.
88 /// timestamp.
89 fn clear_ambiguous_times(
89 fn clear_ambiguous_times(
90 &mut self,
90 &mut self,
91 filenames: Vec<HgPathBuf>,
91 filenames: Vec<HgPathBuf>,
92 now: i32,
92 now: i32,
93 ) -> Result<(), DirstateV2ParseError>;
93 ) -> Result<(), DirstateV2ParseError>;
94
94
95 /// Return whether the map has an "non-normal" entry for the given
95 /// Return whether the map has an "non-normal" entry for the given
96 /// filename. That is, any entry with a `state` other than
96 /// filename. That is, any entry with a `state` other than
97 /// `EntryState::Normal` or with an ambiguous `mtime`.
97 /// `EntryState::Normal` or with an ambiguous `mtime`.
98 fn non_normal_entries_contains(
98 fn non_normal_entries_contains(
99 &mut self,
99 &mut self,
100 key: &HgPath,
100 key: &HgPath,
101 ) -> Result<bool, DirstateV2ParseError>;
101 ) -> Result<bool, DirstateV2ParseError>;
102
102
103 /// Mark the given path as "normal" file. This is only relevant in the flat
103 /// Mark the given path as "normal" file. This is only relevant in the flat
104 /// dirstate map where there is a separate `HashSet` that needs to be kept
104 /// dirstate map where there is a separate `HashSet` that needs to be kept
105 /// up to date.
105 /// up to date.
106 /// Returns whether the key was present in the set.
106 /// Returns whether the key was present in the set.
107 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool;
107 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool;
108
108
109 /// Mark the given path as "non-normal" file.
109 /// Mark the given path as "non-normal" file.
110 /// This is only relevant in the flat dirstate map where there is a
110 /// This is only relevant in the flat dirstate map where there is a
111 /// separate `HashSet` that needs to be kept up to date.
111 /// separate `HashSet` that needs to be kept up to date.
112 fn non_normal_entries_add(&mut self, key: &HgPath);
112 fn non_normal_entries_add(&mut self, key: &HgPath);
113
113
114 /// Return an iterator of paths whose respective entry are either
114 /// Return an iterator of paths whose respective entry are either
115 /// "non-normal" (see `non_normal_entries_contains`) or "from other
115 /// "non-normal" (see `non_normal_entries_contains`) or "from other
116 /// parent".
116 /// parent".
117 ///
117 ///
118 /// If that information is cached, create the cache as needed.
118 /// If that information is cached, create the cache as needed.
119 ///
119 ///
120 /// "From other parent" is defined as `state == Normal && size == -2`.
120 /// "From other parent" is defined as `state == Normal && size == -2`.
121 ///
121 ///
122 /// Because parse errors can happen during iteration, the iterated items
122 /// Because parse errors can happen during iteration, the iterated items
123 /// are `Result`s.
123 /// are `Result`s.
124 fn non_normal_or_other_parent_paths(
124 fn non_normal_or_other_parent_paths(
125 &mut self,
125 &mut self,
126 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>;
126 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>;
127
127
128 /// Create the cache for `non_normal_or_other_parent_paths` if needed.
128 /// Create the cache for `non_normal_or_other_parent_paths` if needed.
129 ///
129 ///
130 /// If `force` is true, the cache is re-created even if it already exists.
130 /// If `force` is true, the cache is re-created even if it already exists.
131 fn set_non_normal_other_parent_entries(&mut self, force: bool);
131 fn set_non_normal_other_parent_entries(&mut self, force: bool);
132
132
133 /// Return an iterator of paths whose respective entry are "non-normal"
133 /// Return an iterator of paths whose respective entry are "non-normal"
134 /// (see `non_normal_entries_contains`).
134 /// (see `non_normal_entries_contains`).
135 ///
135 ///
136 /// If that information is cached, create the cache as needed.
136 /// If that information is cached, create the cache as needed.
137 ///
137 ///
138 /// Because parse errors can happen during iteration, the iterated items
138 /// Because parse errors can happen during iteration, the iterated items
139 /// are `Result`s.
139 /// are `Result`s.
140 fn iter_non_normal_paths(
140 fn iter_non_normal_paths(
141 &mut self,
141 &mut self,
142 ) -> Box<
142 ) -> Box<
143 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
143 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
144 >;
144 >;
145
145
146 /// Same as `iter_non_normal_paths`, but takes `&self` instead of `&mut
146 /// Same as `iter_non_normal_paths`, but takes `&self` instead of `&mut
147 /// self`.
147 /// self`.
148 ///
148 ///
149 /// Panics if a cache is necessary but does not exist yet.
149 /// Panics if a cache is necessary but does not exist yet.
150 fn iter_non_normal_paths_panic(
150 fn iter_non_normal_paths_panic(
151 &self,
151 &self,
152 ) -> Box<
152 ) -> Box<
153 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
153 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
154 >;
154 >;
155
155
156 /// Return an iterator of paths whose respective entry are "from other
156 /// Return an iterator of paths whose respective entry are "from other
157 /// parent".
157 /// parent".
158 ///
158 ///
159 /// If that information is cached, create the cache as needed.
159 /// If that information is cached, create the cache as needed.
160 ///
160 ///
161 /// "From other parent" is defined as `state == Normal && size == -2`.
161 /// "From other parent" is defined as `state == Normal && size == -2`.
162 ///
162 ///
163 /// Because parse errors can happen during iteration, the iterated items
163 /// Because parse errors can happen during iteration, the iterated items
164 /// are `Result`s.
164 /// are `Result`s.
165 fn iter_other_parent_paths(
165 fn iter_other_parent_paths(
166 &mut self,
166 &mut self,
167 ) -> Box<
167 ) -> Box<
168 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
168 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
169 >;
169 >;
170
170
171 /// Returns whether the sub-tree rooted at the given directory contains any
171 /// Returns whether the sub-tree rooted at the given directory contains any
172 /// tracked file.
172 /// tracked file.
173 ///
173 ///
174 /// A file is tracked if it has a `state` other than `EntryState::Removed`.
174 /// A file is tracked if it has a `state` other than `EntryState::Removed`.
175 fn has_tracked_dir(
175 fn has_tracked_dir(
176 &mut self,
176 &mut self,
177 directory: &HgPath,
177 directory: &HgPath,
178 ) -> Result<bool, DirstateError>;
178 ) -> Result<bool, DirstateError>;
179
179
180 /// Returns whether the sub-tree rooted at the given directory contains any
180 /// Returns whether the sub-tree rooted at the given directory contains any
181 /// file with a dirstate entry.
181 /// file with a dirstate entry.
182 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError>;
182 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError>;
183
183
184 /// Clear mtimes that are ambigous with `now` (similar to
184 /// Clear mtimes that are ambigous with `now` (similar to
185 /// `clear_ambiguous_times` but for all files in the dirstate map), and
185 /// `clear_ambiguous_times` but for all files in the dirstate map), and
186 /// serialize bytes to write the `.hg/dirstate` file to disk in dirstate-v1
186 /// serialize bytes to write the `.hg/dirstate` file to disk in dirstate-v1
187 /// format.
187 /// format.
188 fn pack_v1(
188 fn pack_v1(
189 &mut self,
189 &mut self,
190 parents: DirstateParents,
190 parents: DirstateParents,
191 now: Timestamp,
191 now: Timestamp,
192 ) -> Result<Vec<u8>, DirstateError>;
192 ) -> Result<Vec<u8>, DirstateError>;
193
193
194 /// Clear mtimes that are ambigous with `now` (similar to
194 /// Clear mtimes that are ambigous with `now` (similar to
195 /// `clear_ambiguous_times` but for all files in the dirstate map), and
195 /// `clear_ambiguous_times` but for all files in the dirstate map), and
196 /// serialize bytes to write a dirstate data file to disk in dirstate-v2
196 /// serialize bytes to write a dirstate data file to disk in dirstate-v2
197 /// format.
197 /// format.
198 ///
198 ///
199 /// Returns new data and metadata together with whether that data should be
199 /// Returns new data and metadata together with whether that data should be
200 /// appended to the existing data file whose content is at
200 /// appended to the existing data file whose content is at
201 /// `self.on_disk` (true), instead of written to a new data file
201 /// `self.on_disk` (true), instead of written to a new data file
202 /// (false).
202 /// (false).
203 ///
203 ///
204 /// Note: this is only supported by the tree dirstate map.
204 /// Note: this is only supported by the tree dirstate map.
205 fn pack_v2(
205 fn pack_v2(
206 &mut self,
206 &mut self,
207 now: Timestamp,
207 now: Timestamp,
208 can_append: bool,
208 can_append: bool,
209 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError>;
209 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError>;
210
210
211 /// Run the status algorithm.
211 /// Run the status algorithm.
212 ///
212 ///
213 /// This is not sematically a method of the dirstate map, but a different
213 /// This is not sematically a method of the dirstate map, but a different
214 /// algorithm is used for the flat v.s. tree dirstate map so having it in
214 /// algorithm is used for the flat v.s. tree dirstate map so having it in
215 /// this trait enables the same dynamic dispatch as with other methods.
215 /// this trait enables the same dynamic dispatch as with other methods.
216 fn status<'a>(
216 fn status<'a>(
217 &'a mut self,
217 &'a mut self,
218 matcher: &'a (dyn Matcher + Sync),
218 matcher: &'a (dyn Matcher + Sync),
219 root_dir: PathBuf,
219 root_dir: PathBuf,
220 ignore_files: Vec<PathBuf>,
220 ignore_files: Vec<PathBuf>,
221 options: StatusOptions,
221 options: StatusOptions,
222 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
222 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
223
223
224 /// Returns how many files in the dirstate map have a recorded copy source.
224 /// Returns how many files in the dirstate map have a recorded copy source.
225 fn copy_map_len(&self) -> usize;
225 fn copy_map_len(&self) -> usize;
226
226
227 /// Returns an iterator of `(path, copy_source)` for all files that have a
227 /// Returns an iterator of `(path, copy_source)` for all files that have a
228 /// copy source.
228 /// copy source.
229 fn copy_map_iter(&self) -> CopyMapIter<'_>;
229 fn copy_map_iter(&self) -> CopyMapIter<'_>;
230
230
231 /// Returns whether the givef file has a copy source.
231 /// Returns whether the givef file has a copy source.
232 fn copy_map_contains_key(
232 fn copy_map_contains_key(
233 &self,
233 &self,
234 key: &HgPath,
234 key: &HgPath,
235 ) -> Result<bool, DirstateV2ParseError>;
235 ) -> Result<bool, DirstateV2ParseError>;
236
236
237 /// Returns the copy source for the given file.
237 /// Returns the copy source for the given file.
238 fn copy_map_get(
238 fn copy_map_get(
239 &self,
239 &self,
240 key: &HgPath,
240 key: &HgPath,
241 ) -> Result<Option<&HgPath>, DirstateV2ParseError>;
241 ) -> Result<Option<&HgPath>, DirstateV2ParseError>;
242
242
243 /// Removes the recorded copy source if any for the given file, and returns
243 /// Removes the recorded copy source if any for the given file, and returns
244 /// it.
244 /// it.
245 fn copy_map_remove(
245 fn copy_map_remove(
246 &mut self,
246 &mut self,
247 key: &HgPath,
247 key: &HgPath,
248 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError>;
248 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError>;
249
249
250 /// Set the given `value` copy source for the given `key` file.
250 /// Set the given `value` copy source for the given `key` file.
251 fn copy_map_insert(
251 fn copy_map_insert(
252 &mut self,
252 &mut self,
253 key: HgPathBuf,
253 key: HgPathBuf,
254 value: HgPathBuf,
254 value: HgPathBuf,
255 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError>;
255 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError>;
256
256
257 /// Returns the number of files that have an entry.
257 /// Returns the number of files that have an entry.
258 fn len(&self) -> usize;
258 fn len(&self) -> usize;
259
259
260 /// Returns whether the given file has an entry.
260 /// Returns whether the given file has an entry.
261 fn contains_key(&self, key: &HgPath)
261 fn contains_key(&self, key: &HgPath)
262 -> Result<bool, DirstateV2ParseError>;
262 -> Result<bool, DirstateV2ParseError>;
263
263
264 /// Returns the entry, if any, for the given file.
264 /// Returns the entry, if any, for the given file.
265 fn get(
265 fn get(
266 &self,
266 &self,
267 key: &HgPath,
267 key: &HgPath,
268 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError>;
268 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError>;
269
269
270 /// Returns a `(path, entry)` iterator of files that have an entry.
270 /// Returns a `(path, entry)` iterator of files that have an entry.
271 ///
271 ///
272 /// Because parse errors can happen during iteration, the iterated items
272 /// Because parse errors can happen during iteration, the iterated items
273 /// are `Result`s.
273 /// are `Result`s.
274 fn iter(&self) -> StateMapIter<'_>;
274 fn iter(&self) -> StateMapIter<'_>;
275
275
276 /// Returns an iterator of tracked directories.
276 /// Returns an iterator of tracked directories.
277 ///
277 ///
278 /// This is the paths for which `has_tracked_dir` would return true.
278 /// This is the paths for which `has_tracked_dir` would return true.
279 /// Or, in other words, the union of ancestor paths of all paths that have
279 /// Or, in other words, the union of ancestor paths of all paths that have
280 /// an associated entry in a "tracked" state in this dirstate map.
280 /// an associated entry in a "tracked" state in this dirstate map.
281 ///
281 ///
282 /// Because parse errors can happen during iteration, the iterated items
282 /// Because parse errors can happen during iteration, the iterated items
283 /// are `Result`s.
283 /// are `Result`s.
284 fn iter_tracked_dirs(
284 fn iter_tracked_dirs(
285 &mut self,
285 &mut self,
286 ) -> Result<
286 ) -> Result<
287 Box<
287 Box<
288 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
288 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
289 + Send
289 + Send
290 + '_,
290 + '_,
291 >,
291 >,
292 DirstateError,
292 DirstateError,
293 >;
293 >;
294
294
295 /// Return an iterator of `(path, (state, mode, size, mtime))` for every
295 /// Return an iterator of `(path, (state, mode, size, mtime))` for every
296 /// node stored in this dirstate map, for the purpose of the `hg
296 /// node stored in this dirstate map, for the purpose of the `hg
297 /// debugdirstate` command.
297 /// debugdirstate` command.
298 ///
298 ///
299 /// If `all` is true, include nodes that don’t have an entry.
299 /// If `all` is true, include nodes that don’t have an entry.
300 /// For such nodes `state` is the ASCII space.
300 /// For such nodes `state` is the ASCII space.
301 /// An `mtime` may still be present. It is used to optimize `status`.
301 /// An `mtime` may still be present. It is used to optimize `status`.
302 ///
302 ///
303 /// Because parse errors can happen during iteration, the iterated items
303 /// Because parse errors can happen during iteration, the iterated items
304 /// are `Result`s.
304 /// are `Result`s.
305 fn debug_iter(
305 fn debug_iter(
306 &self,
306 &self,
307 all: bool,
307 all: bool,
308 ) -> Box<
308 ) -> Box<
309 dyn Iterator<
309 dyn Iterator<
310 Item = Result<
310 Item = Result<
311 (&HgPath, (u8, i32, i32, i32)),
311 (&HgPath, (u8, i32, i32, i32)),
312 DirstateV2ParseError,
312 DirstateV2ParseError,
313 >,
313 >,
314 > + Send
314 > + Send
315 + '_,
315 + '_,
316 >;
316 >;
317 }
317 }
318
318
319 impl DirstateMapMethods for DirstateMap {
319 impl DirstateMapMethods for DirstateMap {
320 fn clear(&mut self) {
320 fn clear(&mut self) {
321 self.clear()
321 self.clear()
322 }
322 }
323
323
324 /// Used to set a value directory.
324 /// Used to set a value directory.
325 ///
325 ///
326 /// XXX Is temporary during a refactor of V1 dirstate and will disappear
326 /// XXX Is temporary during a refactor of V1 dirstate and will disappear
327 /// shortly.
327 /// shortly.
328 fn set_entry(
328 fn set_entry(
329 &mut self,
329 &mut self,
330 filename: &HgPath,
330 filename: &HgPath,
331 entry: DirstateEntry,
331 entry: DirstateEntry,
332 ) -> Result<(), DirstateV2ParseError> {
332 ) -> Result<(), DirstateV2ParseError> {
333 self.set_entry(&filename, entry);
333 self.set_entry(&filename, entry);
334 Ok(())
334 Ok(())
335 }
335 }
336
336
337 fn add_file(
337 fn add_file(
338 &mut self,
338 &mut self,
339 filename: &HgPath,
339 filename: &HgPath,
340 entry: DirstateEntry,
340 entry: DirstateEntry,
341 added: bool,
341 added: bool,
342 merged: bool,
342 merged: bool,
343 from_p2: bool,
343 from_p2: bool,
344 possibly_dirty: bool,
344 possibly_dirty: bool,
345 ) -> Result<(), DirstateError> {
345 ) -> Result<(), DirstateError> {
346 self.add_file(filename, entry, added, merged, from_p2, possibly_dirty)
346 self.add_file(filename, entry, added, merged, from_p2, possibly_dirty)
347 }
347 }
348
348
349 fn remove_file(
349 fn remove_file(
350 &mut self,
350 &mut self,
351 filename: &HgPath,
351 filename: &HgPath,
352 in_merge: bool,
352 in_merge: bool,
353 ) -> Result<(), DirstateError> {
353 ) -> Result<(), DirstateError> {
354 self.remove_file(filename, in_merge)
354 self.remove_file(filename, in_merge)
355 }
355 }
356
356
357 fn drop_file(&mut self, filename: &HgPath) -> Result<bool, DirstateError> {
357 fn drop_file(&mut self, filename: &HgPath) -> Result<(), DirstateError> {
358 self.drop_file(filename)
358 self.drop_file(filename)
359 }
359 }
360
360
361 fn clear_ambiguous_times(
361 fn clear_ambiguous_times(
362 &mut self,
362 &mut self,
363 filenames: Vec<HgPathBuf>,
363 filenames: Vec<HgPathBuf>,
364 now: i32,
364 now: i32,
365 ) -> Result<(), DirstateV2ParseError> {
365 ) -> Result<(), DirstateV2ParseError> {
366 Ok(self.clear_ambiguous_times(filenames, now))
366 Ok(self.clear_ambiguous_times(filenames, now))
367 }
367 }
368
368
369 fn non_normal_entries_contains(
369 fn non_normal_entries_contains(
370 &mut self,
370 &mut self,
371 key: &HgPath,
371 key: &HgPath,
372 ) -> Result<bool, DirstateV2ParseError> {
372 ) -> Result<bool, DirstateV2ParseError> {
373 let (non_normal, _other_parent) =
373 let (non_normal, _other_parent) =
374 self.get_non_normal_other_parent_entries();
374 self.get_non_normal_other_parent_entries();
375 Ok(non_normal.contains(key))
375 Ok(non_normal.contains(key))
376 }
376 }
377
377
378 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool {
378 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool {
379 self.non_normal_entries_remove(key)
379 self.non_normal_entries_remove(key)
380 }
380 }
381
381
382 fn non_normal_entries_add(&mut self, key: &HgPath) {
382 fn non_normal_entries_add(&mut self, key: &HgPath) {
383 self.non_normal_entries_add(key)
383 self.non_normal_entries_add(key)
384 }
384 }
385
385
386 fn non_normal_or_other_parent_paths(
386 fn non_normal_or_other_parent_paths(
387 &mut self,
387 &mut self,
388 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
388 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
389 {
389 {
390 let (non_normal, other_parent) =
390 let (non_normal, other_parent) =
391 self.get_non_normal_other_parent_entries();
391 self.get_non_normal_other_parent_entries();
392 Box::new(non_normal.union(other_parent).map(|p| Ok(&**p)))
392 Box::new(non_normal.union(other_parent).map(|p| Ok(&**p)))
393 }
393 }
394
394
395 fn set_non_normal_other_parent_entries(&mut self, force: bool) {
395 fn set_non_normal_other_parent_entries(&mut self, force: bool) {
396 self.set_non_normal_other_parent_entries(force)
396 self.set_non_normal_other_parent_entries(force)
397 }
397 }
398
398
399 fn iter_non_normal_paths(
399 fn iter_non_normal_paths(
400 &mut self,
400 &mut self,
401 ) -> Box<
401 ) -> Box<
402 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
402 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
403 > {
403 > {
404 let (non_normal, _other_parent) =
404 let (non_normal, _other_parent) =
405 self.get_non_normal_other_parent_entries();
405 self.get_non_normal_other_parent_entries();
406 Box::new(non_normal.iter().map(|p| Ok(&**p)))
406 Box::new(non_normal.iter().map(|p| Ok(&**p)))
407 }
407 }
408
408
409 fn iter_non_normal_paths_panic(
409 fn iter_non_normal_paths_panic(
410 &self,
410 &self,
411 ) -> Box<
411 ) -> Box<
412 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
412 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
413 > {
413 > {
414 let (non_normal, _other_parent) =
414 let (non_normal, _other_parent) =
415 self.get_non_normal_other_parent_entries_panic();
415 self.get_non_normal_other_parent_entries_panic();
416 Box::new(non_normal.iter().map(|p| Ok(&**p)))
416 Box::new(non_normal.iter().map(|p| Ok(&**p)))
417 }
417 }
418
418
419 fn iter_other_parent_paths(
419 fn iter_other_parent_paths(
420 &mut self,
420 &mut self,
421 ) -> Box<
421 ) -> Box<
422 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
422 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
423 > {
423 > {
424 let (_non_normal, other_parent) =
424 let (_non_normal, other_parent) =
425 self.get_non_normal_other_parent_entries();
425 self.get_non_normal_other_parent_entries();
426 Box::new(other_parent.iter().map(|p| Ok(&**p)))
426 Box::new(other_parent.iter().map(|p| Ok(&**p)))
427 }
427 }
428
428
429 fn has_tracked_dir(
429 fn has_tracked_dir(
430 &mut self,
430 &mut self,
431 directory: &HgPath,
431 directory: &HgPath,
432 ) -> Result<bool, DirstateError> {
432 ) -> Result<bool, DirstateError> {
433 self.has_tracked_dir(directory)
433 self.has_tracked_dir(directory)
434 }
434 }
435
435
436 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
436 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
437 self.has_dir(directory)
437 self.has_dir(directory)
438 }
438 }
439
439
440 fn pack_v1(
440 fn pack_v1(
441 &mut self,
441 &mut self,
442 parents: DirstateParents,
442 parents: DirstateParents,
443 now: Timestamp,
443 now: Timestamp,
444 ) -> Result<Vec<u8>, DirstateError> {
444 ) -> Result<Vec<u8>, DirstateError> {
445 self.pack(parents, now)
445 self.pack(parents, now)
446 }
446 }
447
447
448 fn pack_v2(
448 fn pack_v2(
449 &mut self,
449 &mut self,
450 _now: Timestamp,
450 _now: Timestamp,
451 _can_append: bool,
451 _can_append: bool,
452 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
452 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
453 panic!(
453 panic!(
454 "should have used dirstate_tree::DirstateMap to use the v2 format"
454 "should have used dirstate_tree::DirstateMap to use the v2 format"
455 )
455 )
456 }
456 }
457
457
458 fn status<'a>(
458 fn status<'a>(
459 &'a mut self,
459 &'a mut self,
460 matcher: &'a (dyn Matcher + Sync),
460 matcher: &'a (dyn Matcher + Sync),
461 root_dir: PathBuf,
461 root_dir: PathBuf,
462 ignore_files: Vec<PathBuf>,
462 ignore_files: Vec<PathBuf>,
463 options: StatusOptions,
463 options: StatusOptions,
464 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
464 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
465 {
465 {
466 crate::status(self, matcher, root_dir, ignore_files, options)
466 crate::status(self, matcher, root_dir, ignore_files, options)
467 }
467 }
468
468
469 fn copy_map_len(&self) -> usize {
469 fn copy_map_len(&self) -> usize {
470 self.copy_map.len()
470 self.copy_map.len()
471 }
471 }
472
472
473 fn copy_map_iter(&self) -> CopyMapIter<'_> {
473 fn copy_map_iter(&self) -> CopyMapIter<'_> {
474 Box::new(
474 Box::new(
475 self.copy_map
475 self.copy_map
476 .iter()
476 .iter()
477 .map(|(key, value)| Ok((&**key, &**value))),
477 .map(|(key, value)| Ok((&**key, &**value))),
478 )
478 )
479 }
479 }
480
480
481 fn copy_map_contains_key(
481 fn copy_map_contains_key(
482 &self,
482 &self,
483 key: &HgPath,
483 key: &HgPath,
484 ) -> Result<bool, DirstateV2ParseError> {
484 ) -> Result<bool, DirstateV2ParseError> {
485 Ok(self.copy_map.contains_key(key))
485 Ok(self.copy_map.contains_key(key))
486 }
486 }
487
487
488 fn copy_map_get(
488 fn copy_map_get(
489 &self,
489 &self,
490 key: &HgPath,
490 key: &HgPath,
491 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
491 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
492 Ok(self.copy_map.get(key).map(|p| &**p))
492 Ok(self.copy_map.get(key).map(|p| &**p))
493 }
493 }
494
494
495 fn copy_map_remove(
495 fn copy_map_remove(
496 &mut self,
496 &mut self,
497 key: &HgPath,
497 key: &HgPath,
498 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
498 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
499 Ok(self.copy_map.remove(key))
499 Ok(self.copy_map.remove(key))
500 }
500 }
501
501
502 fn copy_map_insert(
502 fn copy_map_insert(
503 &mut self,
503 &mut self,
504 key: HgPathBuf,
504 key: HgPathBuf,
505 value: HgPathBuf,
505 value: HgPathBuf,
506 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
506 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
507 Ok(self.copy_map.insert(key, value))
507 Ok(self.copy_map.insert(key, value))
508 }
508 }
509
509
510 fn len(&self) -> usize {
510 fn len(&self) -> usize {
511 (&**self).len()
511 (&**self).len()
512 }
512 }
513
513
514 fn contains_key(
514 fn contains_key(
515 &self,
515 &self,
516 key: &HgPath,
516 key: &HgPath,
517 ) -> Result<bool, DirstateV2ParseError> {
517 ) -> Result<bool, DirstateV2ParseError> {
518 Ok((&**self).contains_key(key))
518 Ok((&**self).contains_key(key))
519 }
519 }
520
520
521 fn get(
521 fn get(
522 &self,
522 &self,
523 key: &HgPath,
523 key: &HgPath,
524 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
524 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
525 Ok((&**self).get(key).cloned())
525 Ok((&**self).get(key).cloned())
526 }
526 }
527
527
528 fn iter(&self) -> StateMapIter<'_> {
528 fn iter(&self) -> StateMapIter<'_> {
529 Box::new((&**self).iter().map(|(key, value)| Ok((&**key, *value))))
529 Box::new((&**self).iter().map(|(key, value)| Ok((&**key, *value))))
530 }
530 }
531
531
532 fn iter_tracked_dirs(
532 fn iter_tracked_dirs(
533 &mut self,
533 &mut self,
534 ) -> Result<
534 ) -> Result<
535 Box<
535 Box<
536 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
536 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
537 + Send
537 + Send
538 + '_,
538 + '_,
539 >,
539 >,
540 DirstateError,
540 DirstateError,
541 > {
541 > {
542 self.set_all_dirs()?;
542 self.set_all_dirs()?;
543 Ok(Box::new(
543 Ok(Box::new(
544 self.all_dirs
544 self.all_dirs
545 .as_ref()
545 .as_ref()
546 .unwrap()
546 .unwrap()
547 .iter()
547 .iter()
548 .map(|path| Ok(&**path)),
548 .map(|path| Ok(&**path)),
549 ))
549 ))
550 }
550 }
551
551
552 fn debug_iter(
552 fn debug_iter(
553 &self,
553 &self,
554 all: bool,
554 all: bool,
555 ) -> Box<
555 ) -> Box<
556 dyn Iterator<
556 dyn Iterator<
557 Item = Result<
557 Item = Result<
558 (&HgPath, (u8, i32, i32, i32)),
558 (&HgPath, (u8, i32, i32, i32)),
559 DirstateV2ParseError,
559 DirstateV2ParseError,
560 >,
560 >,
561 > + Send
561 > + Send
562 + '_,
562 + '_,
563 > {
563 > {
564 // Not used for the flat (not tree-based) DirstateMap
564 // Not used for the flat (not tree-based) DirstateMap
565 let _ = all;
565 let _ = all;
566
566
567 Box::new(
567 Box::new(
568 (&**self)
568 (&**self)
569 .iter()
569 .iter()
570 .map(|(path, entry)| Ok((&**path, entry.debug_tuple()))),
570 .map(|(path, entry)| Ok((&**path, entry.debug_tuple()))),
571 )
571 )
572 }
572 }
573 }
573 }
@@ -1,245 +1,245 b''
1 use crate::dirstate::parsers::Timestamp;
1 use crate::dirstate::parsers::Timestamp;
2 use crate::dirstate_tree::dispatch::DirstateMapMethods;
2 use crate::dirstate_tree::dispatch::DirstateMapMethods;
3 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
3 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
4 use crate::dirstate_tree::owning::OwningDirstateMap;
4 use crate::dirstate_tree::owning::OwningDirstateMap;
5 use crate::matchers::Matcher;
5 use crate::matchers::Matcher;
6 use crate::utils::hg_path::{HgPath, HgPathBuf};
6 use crate::utils::hg_path::{HgPath, HgPathBuf};
7 use crate::CopyMapIter;
7 use crate::CopyMapIter;
8 use crate::DirstateEntry;
8 use crate::DirstateEntry;
9 use crate::DirstateError;
9 use crate::DirstateError;
10 use crate::DirstateParents;
10 use crate::DirstateParents;
11 use crate::DirstateStatus;
11 use crate::DirstateStatus;
12 use crate::PatternFileWarning;
12 use crate::PatternFileWarning;
13 use crate::StateMapIter;
13 use crate::StateMapIter;
14 use crate::StatusError;
14 use crate::StatusError;
15 use crate::StatusOptions;
15 use crate::StatusOptions;
16 use std::path::PathBuf;
16 use std::path::PathBuf;
17
17
18 impl DirstateMapMethods for OwningDirstateMap {
18 impl DirstateMapMethods for OwningDirstateMap {
19 fn clear(&mut self) {
19 fn clear(&mut self) {
20 self.get_mut().clear()
20 self.get_mut().clear()
21 }
21 }
22
22
23 fn set_entry(
23 fn set_entry(
24 &mut self,
24 &mut self,
25 filename: &HgPath,
25 filename: &HgPath,
26 entry: DirstateEntry,
26 entry: DirstateEntry,
27 ) -> Result<(), DirstateV2ParseError> {
27 ) -> Result<(), DirstateV2ParseError> {
28 self.get_mut().set_entry(filename, entry)
28 self.get_mut().set_entry(filename, entry)
29 }
29 }
30
30
31 fn add_file(
31 fn add_file(
32 &mut self,
32 &mut self,
33 filename: &HgPath,
33 filename: &HgPath,
34 entry: DirstateEntry,
34 entry: DirstateEntry,
35 added: bool,
35 added: bool,
36 merged: bool,
36 merged: bool,
37 from_p2: bool,
37 from_p2: bool,
38 possibly_dirty: bool,
38 possibly_dirty: bool,
39 ) -> Result<(), DirstateError> {
39 ) -> Result<(), DirstateError> {
40 self.get_mut().add_file(
40 self.get_mut().add_file(
41 filename,
41 filename,
42 entry,
42 entry,
43 added,
43 added,
44 merged,
44 merged,
45 from_p2,
45 from_p2,
46 possibly_dirty,
46 possibly_dirty,
47 )
47 )
48 }
48 }
49
49
50 fn remove_file(
50 fn remove_file(
51 &mut self,
51 &mut self,
52 filename: &HgPath,
52 filename: &HgPath,
53 in_merge: bool,
53 in_merge: bool,
54 ) -> Result<(), DirstateError> {
54 ) -> Result<(), DirstateError> {
55 self.get_mut().remove_file(filename, in_merge)
55 self.get_mut().remove_file(filename, in_merge)
56 }
56 }
57
57
58 fn drop_file(&mut self, filename: &HgPath) -> Result<bool, DirstateError> {
58 fn drop_file(&mut self, filename: &HgPath) -> Result<(), DirstateError> {
59 self.get_mut().drop_file(filename)
59 self.get_mut().drop_file(filename)
60 }
60 }
61
61
62 fn clear_ambiguous_times(
62 fn clear_ambiguous_times(
63 &mut self,
63 &mut self,
64 filenames: Vec<HgPathBuf>,
64 filenames: Vec<HgPathBuf>,
65 now: i32,
65 now: i32,
66 ) -> Result<(), DirstateV2ParseError> {
66 ) -> Result<(), DirstateV2ParseError> {
67 self.get_mut().clear_ambiguous_times(filenames, now)
67 self.get_mut().clear_ambiguous_times(filenames, now)
68 }
68 }
69
69
70 fn non_normal_entries_contains(
70 fn non_normal_entries_contains(
71 &mut self,
71 &mut self,
72 key: &HgPath,
72 key: &HgPath,
73 ) -> Result<bool, DirstateV2ParseError> {
73 ) -> Result<bool, DirstateV2ParseError> {
74 self.get_mut().non_normal_entries_contains(key)
74 self.get_mut().non_normal_entries_contains(key)
75 }
75 }
76
76
77 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool {
77 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool {
78 self.get_mut().non_normal_entries_remove(key)
78 self.get_mut().non_normal_entries_remove(key)
79 }
79 }
80
80
81 fn non_normal_entries_add(&mut self, key: &HgPath) {
81 fn non_normal_entries_add(&mut self, key: &HgPath) {
82 self.get_mut().non_normal_entries_add(key)
82 self.get_mut().non_normal_entries_add(key)
83 }
83 }
84
84
85 fn non_normal_or_other_parent_paths(
85 fn non_normal_or_other_parent_paths(
86 &mut self,
86 &mut self,
87 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
87 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
88 {
88 {
89 self.get_mut().non_normal_or_other_parent_paths()
89 self.get_mut().non_normal_or_other_parent_paths()
90 }
90 }
91
91
92 fn set_non_normal_other_parent_entries(&mut self, force: bool) {
92 fn set_non_normal_other_parent_entries(&mut self, force: bool) {
93 self.get_mut().set_non_normal_other_parent_entries(force)
93 self.get_mut().set_non_normal_other_parent_entries(force)
94 }
94 }
95
95
96 fn iter_non_normal_paths(
96 fn iter_non_normal_paths(
97 &mut self,
97 &mut self,
98 ) -> Box<
98 ) -> Box<
99 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
99 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
100 > {
100 > {
101 self.get_mut().iter_non_normal_paths()
101 self.get_mut().iter_non_normal_paths()
102 }
102 }
103
103
104 fn iter_non_normal_paths_panic(
104 fn iter_non_normal_paths_panic(
105 &self,
105 &self,
106 ) -> Box<
106 ) -> Box<
107 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
107 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
108 > {
108 > {
109 self.get().iter_non_normal_paths_panic()
109 self.get().iter_non_normal_paths_panic()
110 }
110 }
111
111
112 fn iter_other_parent_paths(
112 fn iter_other_parent_paths(
113 &mut self,
113 &mut self,
114 ) -> Box<
114 ) -> Box<
115 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
115 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
116 > {
116 > {
117 self.get_mut().iter_other_parent_paths()
117 self.get_mut().iter_other_parent_paths()
118 }
118 }
119
119
120 fn has_tracked_dir(
120 fn has_tracked_dir(
121 &mut self,
121 &mut self,
122 directory: &HgPath,
122 directory: &HgPath,
123 ) -> Result<bool, DirstateError> {
123 ) -> Result<bool, DirstateError> {
124 self.get_mut().has_tracked_dir(directory)
124 self.get_mut().has_tracked_dir(directory)
125 }
125 }
126
126
127 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
127 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
128 self.get_mut().has_dir(directory)
128 self.get_mut().has_dir(directory)
129 }
129 }
130
130
131 fn pack_v1(
131 fn pack_v1(
132 &mut self,
132 &mut self,
133 parents: DirstateParents,
133 parents: DirstateParents,
134 now: Timestamp,
134 now: Timestamp,
135 ) -> Result<Vec<u8>, DirstateError> {
135 ) -> Result<Vec<u8>, DirstateError> {
136 self.get_mut().pack_v1(parents, now)
136 self.get_mut().pack_v1(parents, now)
137 }
137 }
138
138
139 fn pack_v2(
139 fn pack_v2(
140 &mut self,
140 &mut self,
141 now: Timestamp,
141 now: Timestamp,
142 can_append: bool,
142 can_append: bool,
143 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
143 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
144 self.get_mut().pack_v2(now, can_append)
144 self.get_mut().pack_v2(now, can_append)
145 }
145 }
146
146
147 fn status<'a>(
147 fn status<'a>(
148 &'a mut self,
148 &'a mut self,
149 matcher: &'a (dyn Matcher + Sync),
149 matcher: &'a (dyn Matcher + Sync),
150 root_dir: PathBuf,
150 root_dir: PathBuf,
151 ignore_files: Vec<PathBuf>,
151 ignore_files: Vec<PathBuf>,
152 options: StatusOptions,
152 options: StatusOptions,
153 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
153 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
154 {
154 {
155 self.get_mut()
155 self.get_mut()
156 .status(matcher, root_dir, ignore_files, options)
156 .status(matcher, root_dir, ignore_files, options)
157 }
157 }
158
158
159 fn copy_map_len(&self) -> usize {
159 fn copy_map_len(&self) -> usize {
160 self.get().copy_map_len()
160 self.get().copy_map_len()
161 }
161 }
162
162
163 fn copy_map_iter(&self) -> CopyMapIter<'_> {
163 fn copy_map_iter(&self) -> CopyMapIter<'_> {
164 self.get().copy_map_iter()
164 self.get().copy_map_iter()
165 }
165 }
166
166
167 fn copy_map_contains_key(
167 fn copy_map_contains_key(
168 &self,
168 &self,
169 key: &HgPath,
169 key: &HgPath,
170 ) -> Result<bool, DirstateV2ParseError> {
170 ) -> Result<bool, DirstateV2ParseError> {
171 self.get().copy_map_contains_key(key)
171 self.get().copy_map_contains_key(key)
172 }
172 }
173
173
174 fn copy_map_get(
174 fn copy_map_get(
175 &self,
175 &self,
176 key: &HgPath,
176 key: &HgPath,
177 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
177 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
178 self.get().copy_map_get(key)
178 self.get().copy_map_get(key)
179 }
179 }
180
180
181 fn copy_map_remove(
181 fn copy_map_remove(
182 &mut self,
182 &mut self,
183 key: &HgPath,
183 key: &HgPath,
184 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
184 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
185 self.get_mut().copy_map_remove(key)
185 self.get_mut().copy_map_remove(key)
186 }
186 }
187
187
188 fn copy_map_insert(
188 fn copy_map_insert(
189 &mut self,
189 &mut self,
190 key: HgPathBuf,
190 key: HgPathBuf,
191 value: HgPathBuf,
191 value: HgPathBuf,
192 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
192 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
193 self.get_mut().copy_map_insert(key, value)
193 self.get_mut().copy_map_insert(key, value)
194 }
194 }
195
195
196 fn len(&self) -> usize {
196 fn len(&self) -> usize {
197 self.get().len()
197 self.get().len()
198 }
198 }
199
199
200 fn contains_key(
200 fn contains_key(
201 &self,
201 &self,
202 key: &HgPath,
202 key: &HgPath,
203 ) -> Result<bool, DirstateV2ParseError> {
203 ) -> Result<bool, DirstateV2ParseError> {
204 self.get().contains_key(key)
204 self.get().contains_key(key)
205 }
205 }
206
206
207 fn get(
207 fn get(
208 &self,
208 &self,
209 key: &HgPath,
209 key: &HgPath,
210 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
210 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
211 self.get().get(key)
211 self.get().get(key)
212 }
212 }
213
213
214 fn iter(&self) -> StateMapIter<'_> {
214 fn iter(&self) -> StateMapIter<'_> {
215 self.get().iter()
215 self.get().iter()
216 }
216 }
217
217
218 fn iter_tracked_dirs(
218 fn iter_tracked_dirs(
219 &mut self,
219 &mut self,
220 ) -> Result<
220 ) -> Result<
221 Box<
221 Box<
222 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
222 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
223 + Send
223 + Send
224 + '_,
224 + '_,
225 >,
225 >,
226 DirstateError,
226 DirstateError,
227 > {
227 > {
228 self.get_mut().iter_tracked_dirs()
228 self.get_mut().iter_tracked_dirs()
229 }
229 }
230
230
231 fn debug_iter(
231 fn debug_iter(
232 &self,
232 &self,
233 all: bool,
233 all: bool,
234 ) -> Box<
234 ) -> Box<
235 dyn Iterator<
235 dyn Iterator<
236 Item = Result<
236 Item = Result<
237 (&HgPath, (u8, i32, i32, i32)),
237 (&HgPath, (u8, i32, i32, i32)),
238 DirstateV2ParseError,
238 DirstateV2ParseError,
239 >,
239 >,
240 > + Send
240 > + Send
241 + '_,
241 + '_,
242 > {
242 > {
243 self.get().debug_iter(all)
243 self.get().debug_iter(all)
244 }
244 }
245 }
245 }
@@ -1,674 +1,668 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 PyNone, PyObject, PyResult, PySet, PyString, Python, PythonObject,
17 UnsafePyLeaked,
17 ToPyObject, 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_dirstate_item(
132 def set_dirstate_item(
133 &self,
133 &self,
134 path: PyObject,
134 path: PyObject,
135 item: DirstateItem
135 item: DirstateItem
136 ) -> PyResult<PyObject> {
136 ) -> PyResult<PyObject> {
137 let f = path.extract::<PyBytes>(py)?;
137 let f = path.extract::<PyBytes>(py)?;
138 let filename = HgPath::new(f.data(py));
138 let filename = HgPath::new(f.data(py));
139 self.inner(py)
139 self.inner(py)
140 .borrow_mut()
140 .borrow_mut()
141 .set_entry(filename, item.get_entry(py))
141 .set_entry(filename, item.get_entry(py))
142 .map_err(|e| v2_error(py, e))?;
142 .map_err(|e| v2_error(py, e))?;
143 Ok(py.None())
143 Ok(py.None())
144 }
144 }
145
145
146 def addfile(
146 def addfile(
147 &self,
147 &self,
148 f: PyObject,
148 f: PyObject,
149 mode: PyObject,
149 mode: PyObject,
150 size: PyObject,
150 size: PyObject,
151 mtime: PyObject,
151 mtime: PyObject,
152 added: PyObject,
152 added: PyObject,
153 merged: PyObject,
153 merged: PyObject,
154 from_p2: PyObject,
154 from_p2: PyObject,
155 possibly_dirty: PyObject,
155 possibly_dirty: PyObject,
156 ) -> PyResult<PyObject> {
156 ) -> PyResult<PyObject> {
157 let f = f.extract::<PyBytes>(py)?;
157 let f = f.extract::<PyBytes>(py)?;
158 let filename = HgPath::new(f.data(py));
158 let filename = HgPath::new(f.data(py));
159 let mode = if mode.is_none(py) {
159 let mode = if mode.is_none(py) {
160 // fallback default value
160 // fallback default value
161 0
161 0
162 } else {
162 } else {
163 mode.extract(py)?
163 mode.extract(py)?
164 };
164 };
165 let size = if size.is_none(py) {
165 let size = if size.is_none(py) {
166 // fallback default value
166 // fallback default value
167 SIZE_NON_NORMAL
167 SIZE_NON_NORMAL
168 } else {
168 } else {
169 size.extract(py)?
169 size.extract(py)?
170 };
170 };
171 let mtime = if mtime.is_none(py) {
171 let mtime = if mtime.is_none(py) {
172 // fallback default value
172 // fallback default value
173 MTIME_UNSET
173 MTIME_UNSET
174 } else {
174 } else {
175 mtime.extract(py)?
175 mtime.extract(py)?
176 };
176 };
177 let entry = DirstateEntry::new_for_add_file(mode, size, mtime);
177 let entry = DirstateEntry::new_for_add_file(mode, size, mtime);
178 let added = added.extract::<PyBool>(py)?.is_true();
178 let added = added.extract::<PyBool>(py)?.is_true();
179 let merged = merged.extract::<PyBool>(py)?.is_true();
179 let merged = merged.extract::<PyBool>(py)?.is_true();
180 let from_p2 = from_p2.extract::<PyBool>(py)?.is_true();
180 let from_p2 = from_p2.extract::<PyBool>(py)?.is_true();
181 let possibly_dirty = possibly_dirty.extract::<PyBool>(py)?.is_true();
181 let possibly_dirty = possibly_dirty.extract::<PyBool>(py)?.is_true();
182 self.inner(py).borrow_mut().add_file(
182 self.inner(py).borrow_mut().add_file(
183 filename,
183 filename,
184 entry,
184 entry,
185 added,
185 added,
186 merged,
186 merged,
187 from_p2,
187 from_p2,
188 possibly_dirty
188 possibly_dirty
189 ).and(Ok(py.None())).or_else(|e: DirstateError| {
189 ).and(Ok(py.None())).or_else(|e: DirstateError| {
190 Err(PyErr::new::<exc::ValueError, _>(py, e.to_string()))
190 Err(PyErr::new::<exc::ValueError, _>(py, e.to_string()))
191 })
191 })
192 }
192 }
193
193
194 def removefile(
194 def removefile(
195 &self,
195 &self,
196 f: PyObject,
196 f: PyObject,
197 in_merge: PyObject
197 in_merge: PyObject
198 ) -> PyResult<PyObject> {
198 ) -> PyResult<PyObject> {
199 self.inner(py).borrow_mut()
199 self.inner(py).borrow_mut()
200 .remove_file(
200 .remove_file(
201 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
201 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
202 in_merge.extract::<PyBool>(py)?.is_true(),
202 in_merge.extract::<PyBool>(py)?.is_true(),
203 )
203 )
204 .or_else(|_| {
204 .or_else(|_| {
205 Err(PyErr::new::<exc::OSError, _>(
205 Err(PyErr::new::<exc::OSError, _>(
206 py,
206 py,
207 "Dirstate error".to_string(),
207 "Dirstate error".to_string(),
208 ))
208 ))
209 })?;
209 })?;
210 Ok(py.None())
210 Ok(py.None())
211 }
211 }
212
212
213 def dropfile(
213 def dropfile(
214 &self,
214 &self,
215 f: PyObject,
215 f: PyBytes,
216 ) -> PyResult<PyBool> {
216 ) -> PyResult<PyNone> {
217 self.inner(py).borrow_mut()
217 self.inner(py)
218 .drop_file(
218 .borrow_mut()
219 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
219 .drop_file(HgPath::new(f.data(py)))
220 )
220 .map_err(|e |dirstate_error(py, e))?;
221 .and_then(|b| Ok(b.to_py_object(py)))
221 Ok(PyNone)
222 .or_else(|e| {
223 Err(PyErr::new::<exc::OSError, _>(
224 py,
225 format!("Dirstate error: {}", e.to_string()),
226 ))
227 })
228 }
222 }
229
223
230 def clearambiguoustimes(
224 def clearambiguoustimes(
231 &self,
225 &self,
232 files: PyObject,
226 files: PyObject,
233 now: PyObject
227 now: PyObject
234 ) -> PyResult<PyObject> {
228 ) -> PyResult<PyObject> {
235 let files: PyResult<Vec<HgPathBuf>> = files
229 let files: PyResult<Vec<HgPathBuf>> = files
236 .iter(py)?
230 .iter(py)?
237 .map(|filename| {
231 .map(|filename| {
238 Ok(HgPathBuf::from_bytes(
232 Ok(HgPathBuf::from_bytes(
239 filename?.extract::<PyBytes>(py)?.data(py),
233 filename?.extract::<PyBytes>(py)?.data(py),
240 ))
234 ))
241 })
235 })
242 .collect();
236 .collect();
243 self.inner(py)
237 self.inner(py)
244 .borrow_mut()
238 .borrow_mut()
245 .clear_ambiguous_times(files?, now.extract(py)?)
239 .clear_ambiguous_times(files?, now.extract(py)?)
246 .map_err(|e| v2_error(py, e))?;
240 .map_err(|e| v2_error(py, e))?;
247 Ok(py.None())
241 Ok(py.None())
248 }
242 }
249
243
250 def other_parent_entries(&self) -> PyResult<PyObject> {
244 def other_parent_entries(&self) -> PyResult<PyObject> {
251 let mut inner_shared = self.inner(py).borrow_mut();
245 let mut inner_shared = self.inner(py).borrow_mut();
252 let set = PySet::empty(py)?;
246 let set = PySet::empty(py)?;
253 for path in inner_shared.iter_other_parent_paths() {
247 for path in inner_shared.iter_other_parent_paths() {
254 let path = path.map_err(|e| v2_error(py, e))?;
248 let path = path.map_err(|e| v2_error(py, e))?;
255 set.add(py, PyBytes::new(py, path.as_bytes()))?;
249 set.add(py, PyBytes::new(py, path.as_bytes()))?;
256 }
250 }
257 Ok(set.into_object())
251 Ok(set.into_object())
258 }
252 }
259
253
260 def non_normal_entries(&self) -> PyResult<NonNormalEntries> {
254 def non_normal_entries(&self) -> PyResult<NonNormalEntries> {
261 NonNormalEntries::from_inner(py, self.clone_ref(py))
255 NonNormalEntries::from_inner(py, self.clone_ref(py))
262 }
256 }
263
257
264 def non_normal_entries_contains(&self, key: PyObject) -> PyResult<bool> {
258 def non_normal_entries_contains(&self, key: PyObject) -> PyResult<bool> {
265 let key = key.extract::<PyBytes>(py)?;
259 let key = key.extract::<PyBytes>(py)?;
266 self.inner(py)
260 self.inner(py)
267 .borrow_mut()
261 .borrow_mut()
268 .non_normal_entries_contains(HgPath::new(key.data(py)))
262 .non_normal_entries_contains(HgPath::new(key.data(py)))
269 .map_err(|e| v2_error(py, e))
263 .map_err(|e| v2_error(py, e))
270 }
264 }
271
265
272 def non_normal_entries_display(&self) -> PyResult<PyString> {
266 def non_normal_entries_display(&self) -> PyResult<PyString> {
273 let mut inner = self.inner(py).borrow_mut();
267 let mut inner = self.inner(py).borrow_mut();
274 let paths = inner
268 let paths = inner
275 .iter_non_normal_paths()
269 .iter_non_normal_paths()
276 .collect::<Result<Vec<_>, _>>()
270 .collect::<Result<Vec<_>, _>>()
277 .map_err(|e| v2_error(py, e))?;
271 .map_err(|e| v2_error(py, e))?;
278 let formatted = format!("NonNormalEntries: {}", hg::utils::join_display(paths, ", "));
272 let formatted = format!("NonNormalEntries: {}", hg::utils::join_display(paths, ", "));
279 Ok(PyString::new(py, &formatted))
273 Ok(PyString::new(py, &formatted))
280 }
274 }
281
275
282 def non_normal_entries_remove(&self, key: PyObject) -> PyResult<PyObject> {
276 def non_normal_entries_remove(&self, key: PyObject) -> PyResult<PyObject> {
283 let key = key.extract::<PyBytes>(py)?;
277 let key = key.extract::<PyBytes>(py)?;
284 let key = key.data(py);
278 let key = key.data(py);
285 let was_present = self
279 let was_present = self
286 .inner(py)
280 .inner(py)
287 .borrow_mut()
281 .borrow_mut()
288 .non_normal_entries_remove(HgPath::new(key));
282 .non_normal_entries_remove(HgPath::new(key));
289 if !was_present {
283 if !was_present {
290 let msg = String::from_utf8_lossy(key);
284 let msg = String::from_utf8_lossy(key);
291 Err(PyErr::new::<exc::KeyError, _>(py, msg))
285 Err(PyErr::new::<exc::KeyError, _>(py, msg))
292 } else {
286 } else {
293 Ok(py.None())
287 Ok(py.None())
294 }
288 }
295 }
289 }
296
290
297 def non_normal_entries_discard(&self, key: PyObject) -> PyResult<PyObject>
291 def non_normal_entries_discard(&self, key: PyObject) -> PyResult<PyObject>
298 {
292 {
299 let key = key.extract::<PyBytes>(py)?;
293 let key = key.extract::<PyBytes>(py)?;
300 self
294 self
301 .inner(py)
295 .inner(py)
302 .borrow_mut()
296 .borrow_mut()
303 .non_normal_entries_remove(HgPath::new(key.data(py)));
297 .non_normal_entries_remove(HgPath::new(key.data(py)));
304 Ok(py.None())
298 Ok(py.None())
305 }
299 }
306
300
307 def non_normal_entries_add(&self, key: PyObject) -> PyResult<PyObject> {
301 def non_normal_entries_add(&self, key: PyObject) -> PyResult<PyObject> {
308 let key = key.extract::<PyBytes>(py)?;
302 let key = key.extract::<PyBytes>(py)?;
309 self
303 self
310 .inner(py)
304 .inner(py)
311 .borrow_mut()
305 .borrow_mut()
312 .non_normal_entries_add(HgPath::new(key.data(py)));
306 .non_normal_entries_add(HgPath::new(key.data(py)));
313 Ok(py.None())
307 Ok(py.None())
314 }
308 }
315
309
316 def non_normal_or_other_parent_paths(&self) -> PyResult<PyList> {
310 def non_normal_or_other_parent_paths(&self) -> PyResult<PyList> {
317 let mut inner = self.inner(py).borrow_mut();
311 let mut inner = self.inner(py).borrow_mut();
318
312
319 let ret = PyList::new(py, &[]);
313 let ret = PyList::new(py, &[]);
320 for filename in inner.non_normal_or_other_parent_paths() {
314 for filename in inner.non_normal_or_other_parent_paths() {
321 let filename = filename.map_err(|e| v2_error(py, e))?;
315 let filename = filename.map_err(|e| v2_error(py, e))?;
322 let as_pystring = PyBytes::new(py, filename.as_bytes());
316 let as_pystring = PyBytes::new(py, filename.as_bytes());
323 ret.append(py, as_pystring.into_object());
317 ret.append(py, as_pystring.into_object());
324 }
318 }
325 Ok(ret)
319 Ok(ret)
326 }
320 }
327
321
328 def non_normal_entries_iter(&self) -> PyResult<NonNormalEntriesIterator> {
322 def non_normal_entries_iter(&self) -> PyResult<NonNormalEntriesIterator> {
329 // Make sure the sets are defined before we no longer have a mutable
323 // Make sure the sets are defined before we no longer have a mutable
330 // reference to the dmap.
324 // reference to the dmap.
331 self.inner(py)
325 self.inner(py)
332 .borrow_mut()
326 .borrow_mut()
333 .set_non_normal_other_parent_entries(false);
327 .set_non_normal_other_parent_entries(false);
334
328
335 let leaked_ref = self.inner(py).leak_immutable();
329 let leaked_ref = self.inner(py).leak_immutable();
336
330
337 NonNormalEntriesIterator::from_inner(py, unsafe {
331 NonNormalEntriesIterator::from_inner(py, unsafe {
338 leaked_ref.map(py, |o| {
332 leaked_ref.map(py, |o| {
339 o.iter_non_normal_paths_panic()
333 o.iter_non_normal_paths_panic()
340 })
334 })
341 })
335 })
342 }
336 }
343
337
344 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
338 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
345 let d = d.extract::<PyBytes>(py)?;
339 let d = d.extract::<PyBytes>(py)?;
346 Ok(self.inner(py).borrow_mut()
340 Ok(self.inner(py).borrow_mut()
347 .has_tracked_dir(HgPath::new(d.data(py)))
341 .has_tracked_dir(HgPath::new(d.data(py)))
348 .map_err(|e| {
342 .map_err(|e| {
349 PyErr::new::<exc::ValueError, _>(py, e.to_string())
343 PyErr::new::<exc::ValueError, _>(py, e.to_string())
350 })?
344 })?
351 .to_py_object(py))
345 .to_py_object(py))
352 }
346 }
353
347
354 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
348 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
355 let d = d.extract::<PyBytes>(py)?;
349 let d = d.extract::<PyBytes>(py)?;
356 Ok(self.inner(py).borrow_mut()
350 Ok(self.inner(py).borrow_mut()
357 .has_dir(HgPath::new(d.data(py)))
351 .has_dir(HgPath::new(d.data(py)))
358 .map_err(|e| {
352 .map_err(|e| {
359 PyErr::new::<exc::ValueError, _>(py, e.to_string())
353 PyErr::new::<exc::ValueError, _>(py, e.to_string())
360 })?
354 })?
361 .to_py_object(py))
355 .to_py_object(py))
362 }
356 }
363
357
364 def write_v1(
358 def write_v1(
365 &self,
359 &self,
366 p1: PyObject,
360 p1: PyObject,
367 p2: PyObject,
361 p2: PyObject,
368 now: PyObject
362 now: PyObject
369 ) -> PyResult<PyBytes> {
363 ) -> PyResult<PyBytes> {
370 let now = Timestamp(now.extract(py)?);
364 let now = Timestamp(now.extract(py)?);
371
365
372 let mut inner = self.inner(py).borrow_mut();
366 let mut inner = self.inner(py).borrow_mut();
373 let parents = DirstateParents {
367 let parents = DirstateParents {
374 p1: extract_node_id(py, &p1)?,
368 p1: extract_node_id(py, &p1)?,
375 p2: extract_node_id(py, &p2)?,
369 p2: extract_node_id(py, &p2)?,
376 };
370 };
377 let result = inner.pack_v1(parents, now);
371 let result = inner.pack_v1(parents, now);
378 match result {
372 match result {
379 Ok(packed) => Ok(PyBytes::new(py, &packed)),
373 Ok(packed) => Ok(PyBytes::new(py, &packed)),
380 Err(_) => Err(PyErr::new::<exc::OSError, _>(
374 Err(_) => Err(PyErr::new::<exc::OSError, _>(
381 py,
375 py,
382 "Dirstate error".to_string(),
376 "Dirstate error".to_string(),
383 )),
377 )),
384 }
378 }
385 }
379 }
386
380
387 /// Returns new data together with whether that data should be appended to
381 /// Returns new data together with whether that data should be appended to
388 /// the existing data file whose content is at `self.on_disk` (True),
382 /// the existing data file whose content is at `self.on_disk` (True),
389 /// instead of written to a new data file (False).
383 /// instead of written to a new data file (False).
390 def write_v2(
384 def write_v2(
391 &self,
385 &self,
392 now: PyObject,
386 now: PyObject,
393 can_append: bool,
387 can_append: bool,
394 ) -> PyResult<PyObject> {
388 ) -> PyResult<PyObject> {
395 let now = Timestamp(now.extract(py)?);
389 let now = Timestamp(now.extract(py)?);
396
390
397 let mut inner = self.inner(py).borrow_mut();
391 let mut inner = self.inner(py).borrow_mut();
398 let result = inner.pack_v2(now, can_append);
392 let result = inner.pack_v2(now, can_append);
399 match result {
393 match result {
400 Ok((packed, tree_metadata, append)) => {
394 Ok((packed, tree_metadata, append)) => {
401 let packed = PyBytes::new(py, &packed);
395 let packed = PyBytes::new(py, &packed);
402 let tree_metadata = PyBytes::new(py, &tree_metadata);
396 let tree_metadata = PyBytes::new(py, &tree_metadata);
403 let tuple = (packed, tree_metadata, append);
397 let tuple = (packed, tree_metadata, append);
404 Ok(tuple.to_py_object(py).into_object())
398 Ok(tuple.to_py_object(py).into_object())
405 },
399 },
406 Err(_) => Err(PyErr::new::<exc::OSError, _>(
400 Err(_) => Err(PyErr::new::<exc::OSError, _>(
407 py,
401 py,
408 "Dirstate error".to_string(),
402 "Dirstate error".to_string(),
409 )),
403 )),
410 }
404 }
411 }
405 }
412
406
413 def filefoldmapasdict(&self) -> PyResult<PyDict> {
407 def filefoldmapasdict(&self) -> PyResult<PyDict> {
414 let dict = PyDict::new(py);
408 let dict = PyDict::new(py);
415 for item in self.inner(py).borrow_mut().iter() {
409 for item in self.inner(py).borrow_mut().iter() {
416 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
410 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
417 if entry.state() != EntryState::Removed {
411 if entry.state() != EntryState::Removed {
418 let key = normalize_case(path);
412 let key = normalize_case(path);
419 let value = path;
413 let value = path;
420 dict.set_item(
414 dict.set_item(
421 py,
415 py,
422 PyBytes::new(py, key.as_bytes()).into_object(),
416 PyBytes::new(py, key.as_bytes()).into_object(),
423 PyBytes::new(py, value.as_bytes()).into_object(),
417 PyBytes::new(py, value.as_bytes()).into_object(),
424 )?;
418 )?;
425 }
419 }
426 }
420 }
427 Ok(dict)
421 Ok(dict)
428 }
422 }
429
423
430 def __len__(&self) -> PyResult<usize> {
424 def __len__(&self) -> PyResult<usize> {
431 Ok(self.inner(py).borrow().len())
425 Ok(self.inner(py).borrow().len())
432 }
426 }
433
427
434 def __contains__(&self, key: PyObject) -> PyResult<bool> {
428 def __contains__(&self, key: PyObject) -> PyResult<bool> {
435 let key = key.extract::<PyBytes>(py)?;
429 let key = key.extract::<PyBytes>(py)?;
436 self.inner(py)
430 self.inner(py)
437 .borrow()
431 .borrow()
438 .contains_key(HgPath::new(key.data(py)))
432 .contains_key(HgPath::new(key.data(py)))
439 .map_err(|e| v2_error(py, e))
433 .map_err(|e| v2_error(py, e))
440 }
434 }
441
435
442 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
436 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
443 let key = key.extract::<PyBytes>(py)?;
437 let key = key.extract::<PyBytes>(py)?;
444 let key = HgPath::new(key.data(py));
438 let key = HgPath::new(key.data(py));
445 match self
439 match self
446 .inner(py)
440 .inner(py)
447 .borrow()
441 .borrow()
448 .get(key)
442 .get(key)
449 .map_err(|e| v2_error(py, e))?
443 .map_err(|e| v2_error(py, e))?
450 {
444 {
451 Some(entry) => {
445 Some(entry) => {
452 Ok(DirstateItem::new_as_pyobject(py, entry)?)
446 Ok(DirstateItem::new_as_pyobject(py, entry)?)
453 },
447 },
454 None => Err(PyErr::new::<exc::KeyError, _>(
448 None => Err(PyErr::new::<exc::KeyError, _>(
455 py,
449 py,
456 String::from_utf8_lossy(key.as_bytes()),
450 String::from_utf8_lossy(key.as_bytes()),
457 )),
451 )),
458 }
452 }
459 }
453 }
460
454
461 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
455 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
462 let leaked_ref = self.inner(py).leak_immutable();
456 let leaked_ref = self.inner(py).leak_immutable();
463 DirstateMapKeysIterator::from_inner(
457 DirstateMapKeysIterator::from_inner(
464 py,
458 py,
465 unsafe { leaked_ref.map(py, |o| o.iter()) },
459 unsafe { leaked_ref.map(py, |o| o.iter()) },
466 )
460 )
467 }
461 }
468
462
469 def items(&self) -> PyResult<DirstateMapItemsIterator> {
463 def items(&self) -> PyResult<DirstateMapItemsIterator> {
470 let leaked_ref = self.inner(py).leak_immutable();
464 let leaked_ref = self.inner(py).leak_immutable();
471 DirstateMapItemsIterator::from_inner(
465 DirstateMapItemsIterator::from_inner(
472 py,
466 py,
473 unsafe { leaked_ref.map(py, |o| o.iter()) },
467 unsafe { leaked_ref.map(py, |o| o.iter()) },
474 )
468 )
475 }
469 }
476
470
477 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
471 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
478 let leaked_ref = self.inner(py).leak_immutable();
472 let leaked_ref = self.inner(py).leak_immutable();
479 DirstateMapKeysIterator::from_inner(
473 DirstateMapKeysIterator::from_inner(
480 py,
474 py,
481 unsafe { leaked_ref.map(py, |o| o.iter()) },
475 unsafe { leaked_ref.map(py, |o| o.iter()) },
482 )
476 )
483 }
477 }
484
478
485 // TODO all copymap* methods, see docstring above
479 // TODO all copymap* methods, see docstring above
486 def copymapcopy(&self) -> PyResult<PyDict> {
480 def copymapcopy(&self) -> PyResult<PyDict> {
487 let dict = PyDict::new(py);
481 let dict = PyDict::new(py);
488 for item in self.inner(py).borrow().copy_map_iter() {
482 for item in self.inner(py).borrow().copy_map_iter() {
489 let (key, value) = item.map_err(|e| v2_error(py, e))?;
483 let (key, value) = item.map_err(|e| v2_error(py, e))?;
490 dict.set_item(
484 dict.set_item(
491 py,
485 py,
492 PyBytes::new(py, key.as_bytes()),
486 PyBytes::new(py, key.as_bytes()),
493 PyBytes::new(py, value.as_bytes()),
487 PyBytes::new(py, value.as_bytes()),
494 )?;
488 )?;
495 }
489 }
496 Ok(dict)
490 Ok(dict)
497 }
491 }
498
492
499 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
493 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
500 let key = key.extract::<PyBytes>(py)?;
494 let key = key.extract::<PyBytes>(py)?;
501 match self
495 match self
502 .inner(py)
496 .inner(py)
503 .borrow()
497 .borrow()
504 .copy_map_get(HgPath::new(key.data(py)))
498 .copy_map_get(HgPath::new(key.data(py)))
505 .map_err(|e| v2_error(py, e))?
499 .map_err(|e| v2_error(py, e))?
506 {
500 {
507 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
501 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
508 None => Err(PyErr::new::<exc::KeyError, _>(
502 None => Err(PyErr::new::<exc::KeyError, _>(
509 py,
503 py,
510 String::from_utf8_lossy(key.data(py)),
504 String::from_utf8_lossy(key.data(py)),
511 )),
505 )),
512 }
506 }
513 }
507 }
514 def copymap(&self) -> PyResult<CopyMap> {
508 def copymap(&self) -> PyResult<CopyMap> {
515 CopyMap::from_inner(py, self.clone_ref(py))
509 CopyMap::from_inner(py, self.clone_ref(py))
516 }
510 }
517
511
518 def copymaplen(&self) -> PyResult<usize> {
512 def copymaplen(&self) -> PyResult<usize> {
519 Ok(self.inner(py).borrow().copy_map_len())
513 Ok(self.inner(py).borrow().copy_map_len())
520 }
514 }
521 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
515 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
522 let key = key.extract::<PyBytes>(py)?;
516 let key = key.extract::<PyBytes>(py)?;
523 self.inner(py)
517 self.inner(py)
524 .borrow()
518 .borrow()
525 .copy_map_contains_key(HgPath::new(key.data(py)))
519 .copy_map_contains_key(HgPath::new(key.data(py)))
526 .map_err(|e| v2_error(py, e))
520 .map_err(|e| v2_error(py, e))
527 }
521 }
528 def copymapget(
522 def copymapget(
529 &self,
523 &self,
530 key: PyObject,
524 key: PyObject,
531 default: Option<PyObject>
525 default: Option<PyObject>
532 ) -> PyResult<Option<PyObject>> {
526 ) -> PyResult<Option<PyObject>> {
533 let key = key.extract::<PyBytes>(py)?;
527 let key = key.extract::<PyBytes>(py)?;
534 match self
528 match self
535 .inner(py)
529 .inner(py)
536 .borrow()
530 .borrow()
537 .copy_map_get(HgPath::new(key.data(py)))
531 .copy_map_get(HgPath::new(key.data(py)))
538 .map_err(|e| v2_error(py, e))?
532 .map_err(|e| v2_error(py, e))?
539 {
533 {
540 Some(copy) => Ok(Some(
534 Some(copy) => Ok(Some(
541 PyBytes::new(py, copy.as_bytes()).into_object(),
535 PyBytes::new(py, copy.as_bytes()).into_object(),
542 )),
536 )),
543 None => Ok(default),
537 None => Ok(default),
544 }
538 }
545 }
539 }
546 def copymapsetitem(
540 def copymapsetitem(
547 &self,
541 &self,
548 key: PyObject,
542 key: PyObject,
549 value: PyObject
543 value: PyObject
550 ) -> PyResult<PyObject> {
544 ) -> PyResult<PyObject> {
551 let key = key.extract::<PyBytes>(py)?;
545 let key = key.extract::<PyBytes>(py)?;
552 let value = value.extract::<PyBytes>(py)?;
546 let value = value.extract::<PyBytes>(py)?;
553 self.inner(py)
547 self.inner(py)
554 .borrow_mut()
548 .borrow_mut()
555 .copy_map_insert(
549 .copy_map_insert(
556 HgPathBuf::from_bytes(key.data(py)),
550 HgPathBuf::from_bytes(key.data(py)),
557 HgPathBuf::from_bytes(value.data(py)),
551 HgPathBuf::from_bytes(value.data(py)),
558 )
552 )
559 .map_err(|e| v2_error(py, e))?;
553 .map_err(|e| v2_error(py, e))?;
560 Ok(py.None())
554 Ok(py.None())
561 }
555 }
562 def copymappop(
556 def copymappop(
563 &self,
557 &self,
564 key: PyObject,
558 key: PyObject,
565 default: Option<PyObject>
559 default: Option<PyObject>
566 ) -> PyResult<Option<PyObject>> {
560 ) -> PyResult<Option<PyObject>> {
567 let key = key.extract::<PyBytes>(py)?;
561 let key = key.extract::<PyBytes>(py)?;
568 match self
562 match self
569 .inner(py)
563 .inner(py)
570 .borrow_mut()
564 .borrow_mut()
571 .copy_map_remove(HgPath::new(key.data(py)))
565 .copy_map_remove(HgPath::new(key.data(py)))
572 .map_err(|e| v2_error(py, e))?
566 .map_err(|e| v2_error(py, e))?
573 {
567 {
574 Some(_) => Ok(None),
568 Some(_) => Ok(None),
575 None => Ok(default),
569 None => Ok(default),
576 }
570 }
577 }
571 }
578
572
579 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
573 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
580 let leaked_ref = self.inner(py).leak_immutable();
574 let leaked_ref = self.inner(py).leak_immutable();
581 CopyMapKeysIterator::from_inner(
575 CopyMapKeysIterator::from_inner(
582 py,
576 py,
583 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
577 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
584 )
578 )
585 }
579 }
586
580
587 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
581 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
588 let leaked_ref = self.inner(py).leak_immutable();
582 let leaked_ref = self.inner(py).leak_immutable();
589 CopyMapItemsIterator::from_inner(
583 CopyMapItemsIterator::from_inner(
590 py,
584 py,
591 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
585 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
592 )
586 )
593 }
587 }
594
588
595 def tracked_dirs(&self) -> PyResult<PyList> {
589 def tracked_dirs(&self) -> PyResult<PyList> {
596 let dirs = PyList::new(py, &[]);
590 let dirs = PyList::new(py, &[]);
597 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
591 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
598 .map_err(|e |dirstate_error(py, e))?
592 .map_err(|e |dirstate_error(py, e))?
599 {
593 {
600 let path = path.map_err(|e| v2_error(py, e))?;
594 let path = path.map_err(|e| v2_error(py, e))?;
601 let path = PyBytes::new(py, path.as_bytes());
595 let path = PyBytes::new(py, path.as_bytes());
602 dirs.append(py, path.into_object())
596 dirs.append(py, path.into_object())
603 }
597 }
604 Ok(dirs)
598 Ok(dirs)
605 }
599 }
606
600
607 def debug_iter(&self, all: bool) -> PyResult<PyList> {
601 def debug_iter(&self, all: bool) -> PyResult<PyList> {
608 let dirs = PyList::new(py, &[]);
602 let dirs = PyList::new(py, &[]);
609 for item in self.inner(py).borrow().debug_iter(all) {
603 for item in self.inner(py).borrow().debug_iter(all) {
610 let (path, (state, mode, size, mtime)) =
604 let (path, (state, mode, size, mtime)) =
611 item.map_err(|e| v2_error(py, e))?;
605 item.map_err(|e| v2_error(py, e))?;
612 let path = PyBytes::new(py, path.as_bytes());
606 let path = PyBytes::new(py, path.as_bytes());
613 let item = (path, state, mode, size, mtime);
607 let item = (path, state, mode, size, mtime);
614 dirs.append(py, item.to_py_object(py).into_object())
608 dirs.append(py, item.to_py_object(py).into_object())
615 }
609 }
616 Ok(dirs)
610 Ok(dirs)
617 }
611 }
618 });
612 });
619
613
620 impl DirstateMap {
614 impl DirstateMap {
621 pub fn get_inner_mut<'a>(
615 pub fn get_inner_mut<'a>(
622 &'a self,
616 &'a self,
623 py: Python<'a>,
617 py: Python<'a>,
624 ) -> RefMut<'a, Box<dyn DirstateMapMethods + Send>> {
618 ) -> RefMut<'a, Box<dyn DirstateMapMethods + Send>> {
625 self.inner(py).borrow_mut()
619 self.inner(py).borrow_mut()
626 }
620 }
627 fn translate_key(
621 fn translate_key(
628 py: Python,
622 py: Python,
629 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
623 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
630 ) -> PyResult<Option<PyBytes>> {
624 ) -> PyResult<Option<PyBytes>> {
631 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
625 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
632 Ok(Some(PyBytes::new(py, f.as_bytes())))
626 Ok(Some(PyBytes::new(py, f.as_bytes())))
633 }
627 }
634 fn translate_key_value(
628 fn translate_key_value(
635 py: Python,
629 py: Python,
636 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
630 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
637 ) -> PyResult<Option<(PyBytes, PyObject)>> {
631 ) -> PyResult<Option<(PyBytes, PyObject)>> {
638 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
632 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
639 Ok(Some((
633 Ok(Some((
640 PyBytes::new(py, f.as_bytes()),
634 PyBytes::new(py, f.as_bytes()),
641 DirstateItem::new_as_pyobject(py, entry)?,
635 DirstateItem::new_as_pyobject(py, entry)?,
642 )))
636 )))
643 }
637 }
644 }
638 }
645
639
646 py_shared_iterator!(
640 py_shared_iterator!(
647 DirstateMapKeysIterator,
641 DirstateMapKeysIterator,
648 UnsafePyLeaked<StateMapIter<'static>>,
642 UnsafePyLeaked<StateMapIter<'static>>,
649 DirstateMap::translate_key,
643 DirstateMap::translate_key,
650 Option<PyBytes>
644 Option<PyBytes>
651 );
645 );
652
646
653 py_shared_iterator!(
647 py_shared_iterator!(
654 DirstateMapItemsIterator,
648 DirstateMapItemsIterator,
655 UnsafePyLeaked<StateMapIter<'static>>,
649 UnsafePyLeaked<StateMapIter<'static>>,
656 DirstateMap::translate_key_value,
650 DirstateMap::translate_key_value,
657 Option<(PyBytes, PyObject)>
651 Option<(PyBytes, PyObject)>
658 );
652 );
659
653
660 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
654 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
661 let bytes = obj.extract::<PyBytes>(py)?;
655 let bytes = obj.extract::<PyBytes>(py)?;
662 match bytes.data(py).try_into() {
656 match bytes.data(py).try_into() {
663 Ok(s) => Ok(s),
657 Ok(s) => Ok(s),
664 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
658 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
665 }
659 }
666 }
660 }
667
661
668 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
662 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
669 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
663 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
670 }
664 }
671
665
672 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
666 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
673 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
667 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
674 }
668 }
General Comments 0
You need to be logged in to leave comments. Login now