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