##// END OF EJS Templates
dirstate-v2: Enforce data size read from the docket file...
Simon Sapin -
r48475:48aec076 default
parent child Browse files
Show More
@@ -1,717 +1,719 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 DirstateItem = parsers.DirstateItem
30 DirstateItem = parsers.DirstateItem
31
31
32
32
33 # a special value used internally for `size` if the file come from the other parent
33 # a special value used internally for `size` if the file come from the other parent
34 FROM_P2 = -2
34 FROM_P2 = -2
35
35
36 # a special value used internally for `size` if the file is modified/merged/added
36 # a special value used internally for `size` if the file is modified/merged/added
37 NONNORMAL = -1
37 NONNORMAL = -1
38
38
39 # a special value used internally for `time` if the time is ambigeous
39 # a special value used internally for `time` if the time is ambigeous
40 AMBIGUOUS_TIME = -1
40 AMBIGUOUS_TIME = -1
41
41
42 rangemask = 0x7FFFFFFF
42 rangemask = 0x7FFFFFFF
43
43
44
44
45 class dirstatemap(object):
45 class dirstatemap(object):
46 """Map encapsulating the dirstate's contents.
46 """Map encapsulating the dirstate's contents.
47
47
48 The dirstate contains the following state:
48 The dirstate contains the following state:
49
49
50 - `identity` is the identity of the dirstate file, which can be used to
50 - `identity` is the identity of the dirstate file, which can be used to
51 detect when changes have occurred to the dirstate file.
51 detect when changes have occurred to the dirstate file.
52
52
53 - `parents` is a pair containing the parents of the working copy. The
53 - `parents` is a pair containing the parents of the working copy. The
54 parents are updated by calling `setparents`.
54 parents are updated by calling `setparents`.
55
55
56 - the state map maps filenames to tuples of (state, mode, size, mtime),
56 - the state map maps filenames to tuples of (state, mode, size, mtime),
57 where state is a single character representing 'normal', 'added',
57 where state is a single character representing 'normal', 'added',
58 'removed', or 'merged'. It is read by treating the dirstate as a
58 'removed', or 'merged'. It is read by treating the dirstate as a
59 dict. File state is updated by calling the `addfile`, `removefile` and
59 dict. File state is updated by calling the `addfile`, `removefile` and
60 `dropfile` methods.
60 `dropfile` methods.
61
61
62 - `copymap` maps destination filenames to their source filename.
62 - `copymap` maps destination filenames to their source filename.
63
63
64 The dirstate also provides the following views onto the state:
64 The dirstate also provides the following views onto the state:
65
65
66 - `nonnormalset` is a set of the filenames that have state other
66 - `nonnormalset` is a set of the filenames that have state other
67 than 'normal', or are normal but have an mtime of -1 ('normallookup').
67 than 'normal', or are normal but have an mtime of -1 ('normallookup').
68
68
69 - `otherparentset` is a set of the filenames that are marked as coming
69 - `otherparentset` is a set of the filenames that are marked as coming
70 from the second parent when the dirstate is currently being merged.
70 from the second parent when the dirstate is currently being merged.
71
71
72 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
72 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
73 form that they appear as in the dirstate.
73 form that they appear as in the dirstate.
74
74
75 - `dirfoldmap` is a dict mapping normalized directory names to the
75 - `dirfoldmap` is a dict mapping normalized directory names to the
76 denormalized form that they appear as in the dirstate.
76 denormalized form that they appear as in the dirstate.
77 """
77 """
78
78
79 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
79 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
80 self._ui = ui
80 self._ui = ui
81 self._opener = opener
81 self._opener = opener
82 self._root = root
82 self._root = root
83 self._filename = b'dirstate'
83 self._filename = b'dirstate'
84 self._nodelen = 20
84 self._nodelen = 20
85 self._nodeconstants = nodeconstants
85 self._nodeconstants = nodeconstants
86 assert (
86 assert (
87 not use_dirstate_v2
87 not use_dirstate_v2
88 ), "should have detected unsupported requirement"
88 ), "should have detected unsupported requirement"
89
89
90 self._parents = None
90 self._parents = None
91 self._dirtyparents = False
91 self._dirtyparents = False
92
92
93 # for consistent view between _pl() and _read() invocations
93 # for consistent view between _pl() and _read() invocations
94 self._pendingmode = None
94 self._pendingmode = None
95
95
96 @propertycache
96 @propertycache
97 def _map(self):
97 def _map(self):
98 self._map = {}
98 self._map = {}
99 self.read()
99 self.read()
100 return self._map
100 return self._map
101
101
102 @propertycache
102 @propertycache
103 def copymap(self):
103 def copymap(self):
104 self.copymap = {}
104 self.copymap = {}
105 self._map
105 self._map
106 return self.copymap
106 return self.copymap
107
107
108 def directories(self):
108 def directories(self):
109 # Rust / dirstate-v2 only
109 # Rust / dirstate-v2 only
110 return []
110 return []
111
111
112 def clear(self):
112 def clear(self):
113 self._map.clear()
113 self._map.clear()
114 self.copymap.clear()
114 self.copymap.clear()
115 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
115 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
116 util.clearcachedproperty(self, b"_dirs")
116 util.clearcachedproperty(self, b"_dirs")
117 util.clearcachedproperty(self, b"_alldirs")
117 util.clearcachedproperty(self, b"_alldirs")
118 util.clearcachedproperty(self, b"filefoldmap")
118 util.clearcachedproperty(self, b"filefoldmap")
119 util.clearcachedproperty(self, b"dirfoldmap")
119 util.clearcachedproperty(self, b"dirfoldmap")
120 util.clearcachedproperty(self, b"nonnormalset")
120 util.clearcachedproperty(self, b"nonnormalset")
121 util.clearcachedproperty(self, b"otherparentset")
121 util.clearcachedproperty(self, b"otherparentset")
122
122
123 def items(self):
123 def items(self):
124 return pycompat.iteritems(self._map)
124 return pycompat.iteritems(self._map)
125
125
126 # forward for python2,3 compat
126 # forward for python2,3 compat
127 iteritems = items
127 iteritems = items
128
128
129 def __len__(self):
129 def __len__(self):
130 return len(self._map)
130 return len(self._map)
131
131
132 def __iter__(self):
132 def __iter__(self):
133 return iter(self._map)
133 return iter(self._map)
134
134
135 def get(self, key, default=None):
135 def get(self, key, default=None):
136 return self._map.get(key, default)
136 return self._map.get(key, default)
137
137
138 def __contains__(self, key):
138 def __contains__(self, key):
139 return key in self._map
139 return key in self._map
140
140
141 def __getitem__(self, key):
141 def __getitem__(self, key):
142 return self._map[key]
142 return self._map[key]
143
143
144 def keys(self):
144 def keys(self):
145 return self._map.keys()
145 return self._map.keys()
146
146
147 def preload(self):
147 def preload(self):
148 """Loads the underlying data, if it's not already loaded"""
148 """Loads the underlying data, if it's not already loaded"""
149 self._map
149 self._map
150
150
151 def addfile(
151 def addfile(
152 self,
152 self,
153 f,
153 f,
154 mode=0,
154 mode=0,
155 size=None,
155 size=None,
156 mtime=None,
156 mtime=None,
157 added=False,
157 added=False,
158 merged=False,
158 merged=False,
159 from_p2=False,
159 from_p2=False,
160 possibly_dirty=False,
160 possibly_dirty=False,
161 ):
161 ):
162 """Add a tracked file to the dirstate."""
162 """Add a tracked file to the dirstate."""
163 if added:
163 if added:
164 assert not merged
164 assert not merged
165 assert not possibly_dirty
165 assert not possibly_dirty
166 assert not from_p2
166 assert not from_p2
167 state = b'a'
167 state = b'a'
168 size = NONNORMAL
168 size = NONNORMAL
169 mtime = AMBIGUOUS_TIME
169 mtime = AMBIGUOUS_TIME
170 elif merged:
170 elif merged:
171 assert not possibly_dirty
171 assert not possibly_dirty
172 assert not from_p2
172 assert not from_p2
173 state = b'm'
173 state = b'm'
174 size = FROM_P2
174 size = FROM_P2
175 mtime = AMBIGUOUS_TIME
175 mtime = AMBIGUOUS_TIME
176 elif from_p2:
176 elif from_p2:
177 assert not possibly_dirty
177 assert not possibly_dirty
178 state = b'n'
178 state = b'n'
179 size = FROM_P2
179 size = FROM_P2
180 mtime = AMBIGUOUS_TIME
180 mtime = AMBIGUOUS_TIME
181 elif possibly_dirty:
181 elif possibly_dirty:
182 state = b'n'
182 state = b'n'
183 size = NONNORMAL
183 size = NONNORMAL
184 mtime = AMBIGUOUS_TIME
184 mtime = AMBIGUOUS_TIME
185 else:
185 else:
186 assert size != FROM_P2
186 assert size != FROM_P2
187 assert size != NONNORMAL
187 assert size != NONNORMAL
188 state = b'n'
188 state = b'n'
189 size = size & rangemask
189 size = size & rangemask
190 mtime = mtime & rangemask
190 mtime = mtime & rangemask
191 assert state is not None
191 assert state is not None
192 assert size is not None
192 assert size is not None
193 assert mtime is not None
193 assert mtime is not None
194 old_entry = self.get(f)
194 old_entry = self.get(f)
195 if (
195 if (
196 old_entry is None or old_entry.removed
196 old_entry is None or old_entry.removed
197 ) and "_dirs" in self.__dict__:
197 ) and "_dirs" in self.__dict__:
198 self._dirs.addpath(f)
198 self._dirs.addpath(f)
199 if old_entry is None and "_alldirs" in self.__dict__:
199 if old_entry is None and "_alldirs" in self.__dict__:
200 self._alldirs.addpath(f)
200 self._alldirs.addpath(f)
201 self._map[f] = DirstateItem(state, mode, size, mtime)
201 self._map[f] = DirstateItem(state, mode, size, mtime)
202 if state != b'n' or mtime == AMBIGUOUS_TIME:
202 if state != b'n' or mtime == AMBIGUOUS_TIME:
203 self.nonnormalset.add(f)
203 self.nonnormalset.add(f)
204 if size == FROM_P2:
204 if size == FROM_P2:
205 self.otherparentset.add(f)
205 self.otherparentset.add(f)
206
206
207 def removefile(self, f, in_merge=False):
207 def removefile(self, f, in_merge=False):
208 """
208 """
209 Mark a file as removed in the dirstate.
209 Mark a file as removed in the dirstate.
210
210
211 The `size` parameter is used to store sentinel values that indicate
211 The `size` parameter is used to store sentinel values that indicate
212 the file's previous state. In the future, we should refactor this
212 the file's previous state. In the future, we should refactor this
213 to be more explicit about what that state is.
213 to be more explicit about what that state is.
214 """
214 """
215 entry = self.get(f)
215 entry = self.get(f)
216 size = 0
216 size = 0
217 if in_merge:
217 if in_merge:
218 # XXX we should not be able to have 'm' state and 'FROM_P2' if not
218 # XXX we should not be able to have 'm' state and 'FROM_P2' if not
219 # during a merge. So I (marmoute) am not sure we need the
219 # during a merge. So I (marmoute) am not sure we need the
220 # conditionnal at all. Adding double checking this with assert
220 # conditionnal at all. Adding double checking this with assert
221 # would be nice.
221 # would be nice.
222 if entry is not None:
222 if entry is not None:
223 # backup the previous state
223 # backup the previous state
224 if entry.merged: # merge
224 if entry.merged: # merge
225 size = NONNORMAL
225 size = NONNORMAL
226 elif entry.from_p2:
226 elif entry.from_p2:
227 size = FROM_P2
227 size = FROM_P2
228 self.otherparentset.add(f)
228 self.otherparentset.add(f)
229 if entry is not None and not (entry.merged or entry.from_p2):
229 if entry is not None and not (entry.merged or entry.from_p2):
230 self.copymap.pop(f, None)
230 self.copymap.pop(f, None)
231
231
232 if entry is not None and not entry.removed and "_dirs" in self.__dict__:
232 if entry is not None and not entry.removed and "_dirs" in self.__dict__:
233 self._dirs.delpath(f)
233 self._dirs.delpath(f)
234 if entry is None and "_alldirs" in self.__dict__:
234 if entry is None and "_alldirs" in self.__dict__:
235 self._alldirs.addpath(f)
235 self._alldirs.addpath(f)
236 if "filefoldmap" in self.__dict__:
236 if "filefoldmap" in self.__dict__:
237 normed = util.normcase(f)
237 normed = util.normcase(f)
238 self.filefoldmap.pop(normed, None)
238 self.filefoldmap.pop(normed, None)
239 self._map[f] = DirstateItem(b'r', 0, size, 0)
239 self._map[f] = DirstateItem(b'r', 0, size, 0)
240 self.nonnormalset.add(f)
240 self.nonnormalset.add(f)
241
241
242 def dropfile(self, f):
242 def dropfile(self, f):
243 """
243 """
244 Remove a file from the dirstate. Returns True if the file was
244 Remove a file from the dirstate. Returns True if the file was
245 previously recorded.
245 previously recorded.
246 """
246 """
247 old_entry = self._map.pop(f, None)
247 old_entry = self._map.pop(f, None)
248 exists = False
248 exists = False
249 oldstate = b'?'
249 oldstate = b'?'
250 if old_entry is not None:
250 if old_entry is not None:
251 exists = True
251 exists = True
252 oldstate = old_entry.state
252 oldstate = old_entry.state
253 if exists:
253 if exists:
254 if oldstate != b"r" and "_dirs" in self.__dict__:
254 if oldstate != b"r" and "_dirs" in self.__dict__:
255 self._dirs.delpath(f)
255 self._dirs.delpath(f)
256 if "_alldirs" in self.__dict__:
256 if "_alldirs" in self.__dict__:
257 self._alldirs.delpath(f)
257 self._alldirs.delpath(f)
258 if "filefoldmap" in self.__dict__:
258 if "filefoldmap" in self.__dict__:
259 normed = util.normcase(f)
259 normed = util.normcase(f)
260 self.filefoldmap.pop(normed, None)
260 self.filefoldmap.pop(normed, None)
261 self.nonnormalset.discard(f)
261 self.nonnormalset.discard(f)
262 return exists
262 return exists
263
263
264 def clearambiguoustimes(self, files, now):
264 def clearambiguoustimes(self, files, now):
265 for f in files:
265 for f in files:
266 e = self.get(f)
266 e = self.get(f)
267 if e is not None and e.need_delay(now):
267 if e is not None and e.need_delay(now):
268 e.set_possibly_dirty()
268 e.set_possibly_dirty()
269 self.nonnormalset.add(f)
269 self.nonnormalset.add(f)
270
270
271 def nonnormalentries(self):
271 def nonnormalentries(self):
272 '''Compute the nonnormal dirstate entries from the dmap'''
272 '''Compute the nonnormal dirstate entries from the dmap'''
273 try:
273 try:
274 return parsers.nonnormalotherparententries(self._map)
274 return parsers.nonnormalotherparententries(self._map)
275 except AttributeError:
275 except AttributeError:
276 nonnorm = set()
276 nonnorm = set()
277 otherparent = set()
277 otherparent = set()
278 for fname, e in pycompat.iteritems(self._map):
278 for fname, e in pycompat.iteritems(self._map):
279 if e.state != b'n' or e.mtime == AMBIGUOUS_TIME:
279 if e.state != b'n' or e.mtime == AMBIGUOUS_TIME:
280 nonnorm.add(fname)
280 nonnorm.add(fname)
281 if e.from_p2:
281 if e.from_p2:
282 otherparent.add(fname)
282 otherparent.add(fname)
283 return nonnorm, otherparent
283 return nonnorm, otherparent
284
284
285 @propertycache
285 @propertycache
286 def filefoldmap(self):
286 def filefoldmap(self):
287 """Returns a dictionary mapping normalized case paths to their
287 """Returns a dictionary mapping normalized case paths to their
288 non-normalized versions.
288 non-normalized versions.
289 """
289 """
290 try:
290 try:
291 makefilefoldmap = parsers.make_file_foldmap
291 makefilefoldmap = parsers.make_file_foldmap
292 except AttributeError:
292 except AttributeError:
293 pass
293 pass
294 else:
294 else:
295 return makefilefoldmap(
295 return makefilefoldmap(
296 self._map, util.normcasespec, util.normcasefallback
296 self._map, util.normcasespec, util.normcasefallback
297 )
297 )
298
298
299 f = {}
299 f = {}
300 normcase = util.normcase
300 normcase = util.normcase
301 for name, s in pycompat.iteritems(self._map):
301 for name, s in pycompat.iteritems(self._map):
302 if not s.removed:
302 if not s.removed:
303 f[normcase(name)] = name
303 f[normcase(name)] = name
304 f[b'.'] = b'.' # prevents useless util.fspath() invocation
304 f[b'.'] = b'.' # prevents useless util.fspath() invocation
305 return f
305 return f
306
306
307 def hastrackeddir(self, d):
307 def hastrackeddir(self, d):
308 """
308 """
309 Returns True if the dirstate contains a tracked (not removed) file
309 Returns True if the dirstate contains a tracked (not removed) file
310 in this directory.
310 in this directory.
311 """
311 """
312 return d in self._dirs
312 return d in self._dirs
313
313
314 def hasdir(self, d):
314 def hasdir(self, d):
315 """
315 """
316 Returns True if the dirstate contains a file (tracked or removed)
316 Returns True if the dirstate contains a file (tracked or removed)
317 in this directory.
317 in this directory.
318 """
318 """
319 return d in self._alldirs
319 return d in self._alldirs
320
320
321 @propertycache
321 @propertycache
322 def _dirs(self):
322 def _dirs(self):
323 return pathutil.dirs(self._map, b'r')
323 return pathutil.dirs(self._map, b'r')
324
324
325 @propertycache
325 @propertycache
326 def _alldirs(self):
326 def _alldirs(self):
327 return pathutil.dirs(self._map)
327 return pathutil.dirs(self._map)
328
328
329 def _opendirstatefile(self):
329 def _opendirstatefile(self):
330 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
330 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
331 if self._pendingmode is not None and self._pendingmode != mode:
331 if self._pendingmode is not None and self._pendingmode != mode:
332 fp.close()
332 fp.close()
333 raise error.Abort(
333 raise error.Abort(
334 _(b'working directory state may be changed parallelly')
334 _(b'working directory state may be changed parallelly')
335 )
335 )
336 self._pendingmode = mode
336 self._pendingmode = mode
337 return fp
337 return fp
338
338
339 def parents(self):
339 def parents(self):
340 if not self._parents:
340 if not self._parents:
341 try:
341 try:
342 fp = self._opendirstatefile()
342 fp = self._opendirstatefile()
343 st = fp.read(2 * self._nodelen)
343 st = fp.read(2 * self._nodelen)
344 fp.close()
344 fp.close()
345 except IOError as err:
345 except IOError as err:
346 if err.errno != errno.ENOENT:
346 if err.errno != errno.ENOENT:
347 raise
347 raise
348 # File doesn't exist, so the current state is empty
348 # File doesn't exist, so the current state is empty
349 st = b''
349 st = b''
350
350
351 l = len(st)
351 l = len(st)
352 if l == self._nodelen * 2:
352 if l == self._nodelen * 2:
353 self._parents = (
353 self._parents = (
354 st[: self._nodelen],
354 st[: self._nodelen],
355 st[self._nodelen : 2 * self._nodelen],
355 st[self._nodelen : 2 * self._nodelen],
356 )
356 )
357 elif l == 0:
357 elif l == 0:
358 self._parents = (
358 self._parents = (
359 self._nodeconstants.nullid,
359 self._nodeconstants.nullid,
360 self._nodeconstants.nullid,
360 self._nodeconstants.nullid,
361 )
361 )
362 else:
362 else:
363 raise error.Abort(
363 raise error.Abort(
364 _(b'working directory state appears damaged!')
364 _(b'working directory state appears damaged!')
365 )
365 )
366
366
367 return self._parents
367 return self._parents
368
368
369 def setparents(self, p1, p2):
369 def setparents(self, p1, p2):
370 self._parents = (p1, p2)
370 self._parents = (p1, p2)
371 self._dirtyparents = True
371 self._dirtyparents = True
372
372
373 def read(self):
373 def read(self):
374 # ignore HG_PENDING because identity is used only for writing
374 # ignore HG_PENDING because identity is used only for writing
375 self.identity = util.filestat.frompath(
375 self.identity = util.filestat.frompath(
376 self._opener.join(self._filename)
376 self._opener.join(self._filename)
377 )
377 )
378
378
379 try:
379 try:
380 fp = self._opendirstatefile()
380 fp = self._opendirstatefile()
381 try:
381 try:
382 st = fp.read()
382 st = fp.read()
383 finally:
383 finally:
384 fp.close()
384 fp.close()
385 except IOError as err:
385 except IOError as err:
386 if err.errno != errno.ENOENT:
386 if err.errno != errno.ENOENT:
387 raise
387 raise
388 return
388 return
389 if not st:
389 if not st:
390 return
390 return
391
391
392 if util.safehasattr(parsers, b'dict_new_presized'):
392 if util.safehasattr(parsers, b'dict_new_presized'):
393 # Make an estimate of the number of files in the dirstate based on
393 # Make an estimate of the number of files in the dirstate based on
394 # its size. This trades wasting some memory for avoiding costly
394 # its size. This trades wasting some memory for avoiding costly
395 # resizes. Each entry have a prefix of 17 bytes followed by one or
395 # resizes. Each entry have a prefix of 17 bytes followed by one or
396 # two path names. Studies on various large-scale real-world repositories
396 # two path names. Studies on various large-scale real-world repositories
397 # found 54 bytes a reasonable upper limit for the average path names.
397 # found 54 bytes a reasonable upper limit for the average path names.
398 # Copy entries are ignored for the sake of this estimate.
398 # Copy entries are ignored for the sake of this estimate.
399 self._map = parsers.dict_new_presized(len(st) // 71)
399 self._map = parsers.dict_new_presized(len(st) // 71)
400
400
401 # Python's garbage collector triggers a GC each time a certain number
401 # Python's garbage collector triggers a GC each time a certain number
402 # of container objects (the number being defined by
402 # of container objects (the number being defined by
403 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
403 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
404 # for each file in the dirstate. The C version then immediately marks
404 # for each file in the dirstate. The C version then immediately marks
405 # them as not to be tracked by the collector. However, this has no
405 # them as not to be tracked by the collector. However, this has no
406 # effect on when GCs are triggered, only on what objects the GC looks
406 # effect on when GCs are triggered, only on what objects the GC looks
407 # into. This means that O(number of files) GCs are unavoidable.
407 # into. This means that O(number of files) GCs are unavoidable.
408 # Depending on when in the process's lifetime the dirstate is parsed,
408 # Depending on when in the process's lifetime the dirstate is parsed,
409 # this can get very expensive. As a workaround, disable GC while
409 # this can get very expensive. As a workaround, disable GC while
410 # parsing the dirstate.
410 # parsing the dirstate.
411 #
411 #
412 # (we cannot decorate the function directly since it is in a C module)
412 # (we cannot decorate the function directly since it is in a C module)
413 parse_dirstate = util.nogc(parsers.parse_dirstate)
413 parse_dirstate = util.nogc(parsers.parse_dirstate)
414 p = parse_dirstate(self._map, self.copymap, st)
414 p = parse_dirstate(self._map, self.copymap, st)
415 if not self._dirtyparents:
415 if not self._dirtyparents:
416 self.setparents(*p)
416 self.setparents(*p)
417
417
418 # Avoid excess attribute lookups by fast pathing certain checks
418 # Avoid excess attribute lookups by fast pathing certain checks
419 self.__contains__ = self._map.__contains__
419 self.__contains__ = self._map.__contains__
420 self.__getitem__ = self._map.__getitem__
420 self.__getitem__ = self._map.__getitem__
421 self.get = self._map.get
421 self.get = self._map.get
422
422
423 def write(self, _tr, st, now):
423 def write(self, _tr, st, now):
424 st.write(
424 st.write(
425 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
425 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
426 )
426 )
427 st.close()
427 st.close()
428 self._dirtyparents = False
428 self._dirtyparents = False
429 self.nonnormalset, self.otherparentset = self.nonnormalentries()
429 self.nonnormalset, self.otherparentset = self.nonnormalentries()
430
430
431 @propertycache
431 @propertycache
432 def nonnormalset(self):
432 def nonnormalset(self):
433 nonnorm, otherparents = self.nonnormalentries()
433 nonnorm, otherparents = self.nonnormalentries()
434 self.otherparentset = otherparents
434 self.otherparentset = otherparents
435 return nonnorm
435 return nonnorm
436
436
437 @propertycache
437 @propertycache
438 def otherparentset(self):
438 def otherparentset(self):
439 nonnorm, otherparents = self.nonnormalentries()
439 nonnorm, otherparents = self.nonnormalentries()
440 self.nonnormalset = nonnorm
440 self.nonnormalset = nonnorm
441 return otherparents
441 return otherparents
442
442
443 def non_normal_or_other_parent_paths(self):
443 def non_normal_or_other_parent_paths(self):
444 return self.nonnormalset.union(self.otherparentset)
444 return self.nonnormalset.union(self.otherparentset)
445
445
446 @propertycache
446 @propertycache
447 def identity(self):
447 def identity(self):
448 self._map
448 self._map
449 return self.identity
449 return self.identity
450
450
451 @propertycache
451 @propertycache
452 def dirfoldmap(self):
452 def dirfoldmap(self):
453 f = {}
453 f = {}
454 normcase = util.normcase
454 normcase = util.normcase
455 for name in self._dirs:
455 for name in self._dirs:
456 f[normcase(name)] = name
456 f[normcase(name)] = name
457 return f
457 return f
458
458
459
459
460 if rustmod is not None:
460 if rustmod is not None:
461
461
462 class dirstatemap(object):
462 class dirstatemap(object):
463 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
463 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
464 self._use_dirstate_v2 = use_dirstate_v2
464 self._use_dirstate_v2 = use_dirstate_v2
465 self._nodeconstants = nodeconstants
465 self._nodeconstants = nodeconstants
466 self._ui = ui
466 self._ui = ui
467 self._opener = opener
467 self._opener = opener
468 self._root = root
468 self._root = root
469 self._filename = b'dirstate'
469 self._filename = b'dirstate'
470 self._nodelen = 20 # Also update Rust code when changing this!
470 self._nodelen = 20 # Also update Rust code when changing this!
471 self._parents = None
471 self._parents = None
472 self._dirtyparents = False
472 self._dirtyparents = False
473 self._docket = None
473 self._docket = None
474
474
475 # for consistent view between _pl() and _read() invocations
475 # for consistent view between _pl() and _read() invocations
476 self._pendingmode = None
476 self._pendingmode = None
477
477
478 self._use_dirstate_tree = self._ui.configbool(
478 self._use_dirstate_tree = self._ui.configbool(
479 b"experimental",
479 b"experimental",
480 b"dirstate-tree.in-memory",
480 b"dirstate-tree.in-memory",
481 False,
481 False,
482 )
482 )
483
483
484 def addfile(
484 def addfile(
485 self,
485 self,
486 f,
486 f,
487 mode=0,
487 mode=0,
488 size=None,
488 size=None,
489 mtime=None,
489 mtime=None,
490 added=False,
490 added=False,
491 merged=False,
491 merged=False,
492 from_p2=False,
492 from_p2=False,
493 possibly_dirty=False,
493 possibly_dirty=False,
494 ):
494 ):
495 return self._rustmap.addfile(
495 return self._rustmap.addfile(
496 f,
496 f,
497 mode,
497 mode,
498 size,
498 size,
499 mtime,
499 mtime,
500 added,
500 added,
501 merged,
501 merged,
502 from_p2,
502 from_p2,
503 possibly_dirty,
503 possibly_dirty,
504 )
504 )
505
505
506 def removefile(self, *args, **kwargs):
506 def removefile(self, *args, **kwargs):
507 return self._rustmap.removefile(*args, **kwargs)
507 return self._rustmap.removefile(*args, **kwargs)
508
508
509 def dropfile(self, *args, **kwargs):
509 def dropfile(self, *args, **kwargs):
510 return self._rustmap.dropfile(*args, **kwargs)
510 return self._rustmap.dropfile(*args, **kwargs)
511
511
512 def clearambiguoustimes(self, *args, **kwargs):
512 def clearambiguoustimes(self, *args, **kwargs):
513 return self._rustmap.clearambiguoustimes(*args, **kwargs)
513 return self._rustmap.clearambiguoustimes(*args, **kwargs)
514
514
515 def nonnormalentries(self):
515 def nonnormalentries(self):
516 return self._rustmap.nonnormalentries()
516 return self._rustmap.nonnormalentries()
517
517
518 def get(self, *args, **kwargs):
518 def get(self, *args, **kwargs):
519 return self._rustmap.get(*args, **kwargs)
519 return self._rustmap.get(*args, **kwargs)
520
520
521 @property
521 @property
522 def copymap(self):
522 def copymap(self):
523 return self._rustmap.copymap()
523 return self._rustmap.copymap()
524
524
525 def directories(self):
525 def directories(self):
526 return self._rustmap.directories()
526 return self._rustmap.directories()
527
527
528 def preload(self):
528 def preload(self):
529 self._rustmap
529 self._rustmap
530
530
531 def clear(self):
531 def clear(self):
532 self._rustmap.clear()
532 self._rustmap.clear()
533 self.setparents(
533 self.setparents(
534 self._nodeconstants.nullid, self._nodeconstants.nullid
534 self._nodeconstants.nullid, self._nodeconstants.nullid
535 )
535 )
536 util.clearcachedproperty(self, b"_dirs")
536 util.clearcachedproperty(self, b"_dirs")
537 util.clearcachedproperty(self, b"_alldirs")
537 util.clearcachedproperty(self, b"_alldirs")
538 util.clearcachedproperty(self, b"dirfoldmap")
538 util.clearcachedproperty(self, b"dirfoldmap")
539
539
540 def items(self):
540 def items(self):
541 return self._rustmap.items()
541 return self._rustmap.items()
542
542
543 def keys(self):
543 def keys(self):
544 return iter(self._rustmap)
544 return iter(self._rustmap)
545
545
546 def __contains__(self, key):
546 def __contains__(self, key):
547 return key in self._rustmap
547 return key in self._rustmap
548
548
549 def __getitem__(self, item):
549 def __getitem__(self, item):
550 return self._rustmap[item]
550 return self._rustmap[item]
551
551
552 def __len__(self):
552 def __len__(self):
553 return len(self._rustmap)
553 return len(self._rustmap)
554
554
555 def __iter__(self):
555 def __iter__(self):
556 return iter(self._rustmap)
556 return iter(self._rustmap)
557
557
558 # forward for python2,3 compat
558 # forward for python2,3 compat
559 iteritems = items
559 iteritems = items
560
560
561 def _opendirstatefile(self):
561 def _opendirstatefile(self):
562 fp, mode = txnutil.trypending(
562 fp, mode = txnutil.trypending(
563 self._root, self._opener, self._filename
563 self._root, self._opener, self._filename
564 )
564 )
565 if self._pendingmode is not None and self._pendingmode != mode:
565 if self._pendingmode is not None and self._pendingmode != mode:
566 fp.close()
566 fp.close()
567 raise error.Abort(
567 raise error.Abort(
568 _(b'working directory state may be changed parallelly')
568 _(b'working directory state may be changed parallelly')
569 )
569 )
570 self._pendingmode = mode
570 self._pendingmode = mode
571 return fp
571 return fp
572
572
573 def _readdirstatefile(self, size=-1):
573 def _readdirstatefile(self, size=-1):
574 try:
574 try:
575 with self._opendirstatefile() as fp:
575 with self._opendirstatefile() as fp:
576 return fp.read(size)
576 return fp.read(size)
577 except IOError as err:
577 except IOError as err:
578 if err.errno != errno.ENOENT:
578 if err.errno != errno.ENOENT:
579 raise
579 raise
580 # File doesn't exist, so the current state is empty
580 # File doesn't exist, so the current state is empty
581 return b''
581 return b''
582
582
583 def setparents(self, p1, p2):
583 def setparents(self, p1, p2):
584 self._parents = (p1, p2)
584 self._parents = (p1, p2)
585 self._dirtyparents = True
585 self._dirtyparents = True
586
586
587 def parents(self):
587 def parents(self):
588 if not self._parents:
588 if not self._parents:
589 if self._use_dirstate_v2:
589 if self._use_dirstate_v2:
590 self._parents = self.docket.parents
590 self._parents = self.docket.parents
591 else:
591 else:
592 read_len = self._nodelen * 2
592 read_len = self._nodelen * 2
593 st = self._readdirstatefile(read_len)
593 st = self._readdirstatefile(read_len)
594 l = len(st)
594 l = len(st)
595 if l == read_len:
595 if l == read_len:
596 self._parents = (
596 self._parents = (
597 st[: self._nodelen],
597 st[: self._nodelen],
598 st[self._nodelen : 2 * self._nodelen],
598 st[self._nodelen : 2 * self._nodelen],
599 )
599 )
600 elif l == 0:
600 elif l == 0:
601 self._parents = (
601 self._parents = (
602 self._nodeconstants.nullid,
602 self._nodeconstants.nullid,
603 self._nodeconstants.nullid,
603 self._nodeconstants.nullid,
604 )
604 )
605 else:
605 else:
606 raise error.Abort(
606 raise error.Abort(
607 _(b'working directory state appears damaged!')
607 _(b'working directory state appears damaged!')
608 )
608 )
609
609
610 return self._parents
610 return self._parents
611
611
612 @property
612 @property
613 def docket(self):
613 def docket(self):
614 if not self._docket:
614 if not self._docket:
615 if not self._use_dirstate_v2:
615 if not self._use_dirstate_v2:
616 raise error.ProgrammingError(
616 raise error.ProgrammingError(
617 b'dirstate only has a docket in v2 format'
617 b'dirstate only has a docket in v2 format'
618 )
618 )
619 self._docket = docketmod.DirstateDocket.parse(
619 self._docket = docketmod.DirstateDocket.parse(
620 self._readdirstatefile(), self._nodeconstants
620 self._readdirstatefile(), self._nodeconstants
621 )
621 )
622 return self._docket
622 return self._docket
623
623
624 @propertycache
624 @propertycache
625 def _rustmap(self):
625 def _rustmap(self):
626 """
626 """
627 Fills the Dirstatemap when called.
627 Fills the Dirstatemap when called.
628 """
628 """
629 # ignore HG_PENDING because identity is used only for writing
629 # ignore HG_PENDING because identity is used only for writing
630 self.identity = util.filestat.frompath(
630 self.identity = util.filestat.frompath(
631 self._opener.join(self._filename)
631 self._opener.join(self._filename)
632 )
632 )
633
633
634 if self._use_dirstate_v2:
634 if self._use_dirstate_v2:
635 if self.docket.uuid:
635 if self.docket.uuid:
636 # TODO: use mmap when possible
636 # TODO: use mmap when possible
637 data = self._opener.read(self.docket.data_filename())
637 data = self._opener.read(self.docket.data_filename())
638 else:
638 else:
639 data = b''
639 data = b''
640 self._rustmap = rustmod.DirstateMap.new_v2(data)
640 self._rustmap = rustmod.DirstateMap.new_v2(
641 data, self.docket.data_size
642 )
641 parents = self.docket.parents
643 parents = self.docket.parents
642 else:
644 else:
643 self._rustmap, parents = rustmod.DirstateMap.new_v1(
645 self._rustmap, parents = rustmod.DirstateMap.new_v1(
644 self._use_dirstate_tree, self._readdirstatefile()
646 self._use_dirstate_tree, self._readdirstatefile()
645 )
647 )
646
648
647 if parents and not self._dirtyparents:
649 if parents and not self._dirtyparents:
648 self.setparents(*parents)
650 self.setparents(*parents)
649
651
650 self.__contains__ = self._rustmap.__contains__
652 self.__contains__ = self._rustmap.__contains__
651 self.__getitem__ = self._rustmap.__getitem__
653 self.__getitem__ = self._rustmap.__getitem__
652 self.get = self._rustmap.get
654 self.get = self._rustmap.get
653 return self._rustmap
655 return self._rustmap
654
656
655 def write(self, tr, st, now):
657 def write(self, tr, st, now):
656 if self._use_dirstate_v2:
658 if self._use_dirstate_v2:
657 packed = self._rustmap.write_v2(now)
659 packed = self._rustmap.write_v2(now)
658 old_docket = self.docket
660 old_docket = self.docket
659 new_docket = docketmod.DirstateDocket.with_new_uuid(
661 new_docket = docketmod.DirstateDocket.with_new_uuid(
660 self.parents(), len(packed)
662 self.parents(), len(packed)
661 )
663 )
662 self._opener.write(new_docket.data_filename(), packed)
664 self._opener.write(new_docket.data_filename(), packed)
663 # Write the new docket after the new data file has been
665 # Write the new docket after the new data file has been
664 # written. Because `st` was opened with `atomictemp=True`,
666 # written. Because `st` was opened with `atomictemp=True`,
665 # the actual `.hg/dirstate` file is only affected on close.
667 # the actual `.hg/dirstate` file is only affected on close.
666 st.write(new_docket.serialize())
668 st.write(new_docket.serialize())
667 st.close()
669 st.close()
668 # Remove the old data file after the new docket pointing to
670 # Remove the old data file after the new docket pointing to
669 # the new data file was written.
671 # the new data file was written.
670 if old_docket.uuid:
672 if old_docket.uuid:
671 self._opener.unlink(old_docket.data_filename())
673 self._opener.unlink(old_docket.data_filename())
672 self._docket = new_docket
674 self._docket = new_docket
673 else:
675 else:
674 p1, p2 = self.parents()
676 p1, p2 = self.parents()
675 packed = self._rustmap.write_v1(p1, p2, now)
677 packed = self._rustmap.write_v1(p1, p2, now)
676 st.write(packed)
678 st.write(packed)
677 st.close()
679 st.close()
678 self._dirtyparents = False
680 self._dirtyparents = False
679
681
680 @propertycache
682 @propertycache
681 def filefoldmap(self):
683 def filefoldmap(self):
682 """Returns a dictionary mapping normalized case paths to their
684 """Returns a dictionary mapping normalized case paths to their
683 non-normalized versions.
685 non-normalized versions.
684 """
686 """
685 return self._rustmap.filefoldmapasdict()
687 return self._rustmap.filefoldmapasdict()
686
688
687 def hastrackeddir(self, d):
689 def hastrackeddir(self, d):
688 return self._rustmap.hastrackeddir(d)
690 return self._rustmap.hastrackeddir(d)
689
691
690 def hasdir(self, d):
692 def hasdir(self, d):
691 return self._rustmap.hasdir(d)
693 return self._rustmap.hasdir(d)
692
694
693 @propertycache
695 @propertycache
694 def identity(self):
696 def identity(self):
695 self._rustmap
697 self._rustmap
696 return self.identity
698 return self.identity
697
699
698 @property
700 @property
699 def nonnormalset(self):
701 def nonnormalset(self):
700 nonnorm = self._rustmap.non_normal_entries()
702 nonnorm = self._rustmap.non_normal_entries()
701 return nonnorm
703 return nonnorm
702
704
703 @propertycache
705 @propertycache
704 def otherparentset(self):
706 def otherparentset(self):
705 otherparents = self._rustmap.other_parent_entries()
707 otherparents = self._rustmap.other_parent_entries()
706 return otherparents
708 return otherparents
707
709
708 def non_normal_or_other_parent_paths(self):
710 def non_normal_or_other_parent_paths(self):
709 return self._rustmap.non_normal_or_other_parent_paths()
711 return self._rustmap.non_normal_or_other_parent_paths()
710
712
711 @propertycache
713 @propertycache
712 def dirfoldmap(self):
714 def dirfoldmap(self):
713 f = {}
715 f = {}
714 normcase = util.normcase
716 normcase = util.normcase
715 for name, _pseudo_entry in self.directories():
717 for name, _pseudo_entry in self.directories():
716 f[normcase(name)] = name
718 f[normcase(name)] = name
717 return f
719 return f
@@ -1,1200 +1,1207 b''
1 use bytes_cast::BytesCast;
1 use bytes_cast::BytesCast;
2 use micro_timer::timed;
2 use micro_timer::timed;
3 use std::borrow::Cow;
3 use std::borrow::Cow;
4 use std::convert::TryInto;
4 use std::convert::TryInto;
5 use std::path::PathBuf;
5 use std::path::PathBuf;
6
6
7 use super::on_disk;
7 use super::on_disk;
8 use super::on_disk::DirstateV2ParseError;
8 use super::on_disk::DirstateV2ParseError;
9 use super::path_with_basename::WithBasename;
9 use super::path_with_basename::WithBasename;
10 use crate::dirstate::parsers::pack_entry;
10 use crate::dirstate::parsers::pack_entry;
11 use crate::dirstate::parsers::packed_entry_size;
11 use crate::dirstate::parsers::packed_entry_size;
12 use crate::dirstate::parsers::parse_dirstate_entries;
12 use crate::dirstate::parsers::parse_dirstate_entries;
13 use crate::dirstate::parsers::Timestamp;
13 use crate::dirstate::parsers::Timestamp;
14 use crate::dirstate::MTIME_UNSET;
14 use crate::dirstate::MTIME_UNSET;
15 use crate::dirstate::SIZE_FROM_OTHER_PARENT;
15 use crate::dirstate::SIZE_FROM_OTHER_PARENT;
16 use crate::dirstate::SIZE_NON_NORMAL;
16 use crate::dirstate::SIZE_NON_NORMAL;
17 use crate::dirstate::V1_RANGEMASK;
17 use crate::dirstate::V1_RANGEMASK;
18 use crate::matchers::Matcher;
18 use crate::matchers::Matcher;
19 use crate::utils::hg_path::{HgPath, HgPathBuf};
19 use crate::utils::hg_path::{HgPath, HgPathBuf};
20 use crate::CopyMapIter;
20 use crate::CopyMapIter;
21 use crate::DirstateEntry;
21 use crate::DirstateEntry;
22 use crate::DirstateError;
22 use crate::DirstateError;
23 use crate::DirstateParents;
23 use crate::DirstateParents;
24 use crate::DirstateStatus;
24 use crate::DirstateStatus;
25 use crate::EntryState;
25 use crate::EntryState;
26 use crate::FastHashMap;
26 use crate::FastHashMap;
27 use crate::PatternFileWarning;
27 use crate::PatternFileWarning;
28 use crate::StateMapIter;
28 use crate::StateMapIter;
29 use crate::StatusError;
29 use crate::StatusError;
30 use crate::StatusOptions;
30 use crate::StatusOptions;
31
31
32 pub struct DirstateMap<'on_disk> {
32 pub struct DirstateMap<'on_disk> {
33 /// Contents of the `.hg/dirstate` file
33 /// Contents of the `.hg/dirstate` file
34 pub(super) on_disk: &'on_disk [u8],
34 pub(super) on_disk: &'on_disk [u8],
35
35
36 pub(super) root: ChildNodes<'on_disk>,
36 pub(super) root: ChildNodes<'on_disk>,
37
37
38 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
38 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
39 pub(super) nodes_with_entry_count: u32,
39 pub(super) nodes_with_entry_count: u32,
40
40
41 /// Number of nodes anywhere in the tree that have
41 /// Number of nodes anywhere in the tree that have
42 /// `.copy_source.is_some()`.
42 /// `.copy_source.is_some()`.
43 pub(super) nodes_with_copy_source_count: u32,
43 pub(super) nodes_with_copy_source_count: u32,
44
44
45 /// See on_disk::Header
45 /// See on_disk::Header
46 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
46 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
47 }
47 }
48
48
49 /// Using a plain `HgPathBuf` of the full path from the repository root as a
49 /// Using a plain `HgPathBuf` of the full path from the repository root as a
50 /// map key would also work: all paths in a given map have the same parent
50 /// map key would also work: all paths in a given map have the same parent
51 /// path, so comparing full paths gives the same result as comparing base
51 /// path, so comparing full paths gives the same result as comparing base
52 /// names. However `HashMap` would waste time always re-hashing the same
52 /// names. However `HashMap` would waste time always re-hashing the same
53 /// string prefix.
53 /// string prefix.
54 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
54 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
55
55
56 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
56 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
57 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
57 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
58 pub(super) enum BorrowedPath<'tree, 'on_disk> {
58 pub(super) enum BorrowedPath<'tree, 'on_disk> {
59 InMemory(&'tree HgPathBuf),
59 InMemory(&'tree HgPathBuf),
60 OnDisk(&'on_disk HgPath),
60 OnDisk(&'on_disk HgPath),
61 }
61 }
62
62
63 pub(super) enum ChildNodes<'on_disk> {
63 pub(super) enum ChildNodes<'on_disk> {
64 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
64 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
65 OnDisk(&'on_disk [on_disk::Node]),
65 OnDisk(&'on_disk [on_disk::Node]),
66 }
66 }
67
67
68 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
68 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
69 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
69 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
70 OnDisk(&'on_disk [on_disk::Node]),
70 OnDisk(&'on_disk [on_disk::Node]),
71 }
71 }
72
72
73 pub(super) enum NodeRef<'tree, 'on_disk> {
73 pub(super) enum NodeRef<'tree, 'on_disk> {
74 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
74 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
75 OnDisk(&'on_disk on_disk::Node),
75 OnDisk(&'on_disk on_disk::Node),
76 }
76 }
77
77
78 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
78 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
79 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
79 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
80 match *self {
80 match *self {
81 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
81 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
82 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
82 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
83 }
83 }
84 }
84 }
85 }
85 }
86
86
87 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
87 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
88 type Target = HgPath;
88 type Target = HgPath;
89
89
90 fn deref(&self) -> &HgPath {
90 fn deref(&self) -> &HgPath {
91 match *self {
91 match *self {
92 BorrowedPath::InMemory(in_memory) => in_memory,
92 BorrowedPath::InMemory(in_memory) => in_memory,
93 BorrowedPath::OnDisk(on_disk) => on_disk,
93 BorrowedPath::OnDisk(on_disk) => on_disk,
94 }
94 }
95 }
95 }
96 }
96 }
97
97
98 impl Default for ChildNodes<'_> {
98 impl Default for ChildNodes<'_> {
99 fn default() -> Self {
99 fn default() -> Self {
100 ChildNodes::InMemory(Default::default())
100 ChildNodes::InMemory(Default::default())
101 }
101 }
102 }
102 }
103
103
104 impl<'on_disk> ChildNodes<'on_disk> {
104 impl<'on_disk> ChildNodes<'on_disk> {
105 pub(super) fn as_ref<'tree>(
105 pub(super) fn as_ref<'tree>(
106 &'tree self,
106 &'tree self,
107 ) -> ChildNodesRef<'tree, 'on_disk> {
107 ) -> ChildNodesRef<'tree, 'on_disk> {
108 match self {
108 match self {
109 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
109 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
110 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
110 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
111 }
111 }
112 }
112 }
113
113
114 pub(super) fn is_empty(&self) -> bool {
114 pub(super) fn is_empty(&self) -> bool {
115 match self {
115 match self {
116 ChildNodes::InMemory(nodes) => nodes.is_empty(),
116 ChildNodes::InMemory(nodes) => nodes.is_empty(),
117 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
117 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
118 }
118 }
119 }
119 }
120
120
121 pub(super) fn make_mut(
121 pub(super) fn make_mut(
122 &mut self,
122 &mut self,
123 on_disk: &'on_disk [u8],
123 on_disk: &'on_disk [u8],
124 ) -> Result<
124 ) -> Result<
125 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
125 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
126 DirstateV2ParseError,
126 DirstateV2ParseError,
127 > {
127 > {
128 match self {
128 match self {
129 ChildNodes::InMemory(nodes) => Ok(nodes),
129 ChildNodes::InMemory(nodes) => Ok(nodes),
130 ChildNodes::OnDisk(nodes) => {
130 ChildNodes::OnDisk(nodes) => {
131 let nodes = nodes
131 let nodes = nodes
132 .iter()
132 .iter()
133 .map(|node| {
133 .map(|node| {
134 Ok((
134 Ok((
135 node.path(on_disk)?,
135 node.path(on_disk)?,
136 node.to_in_memory_node(on_disk)?,
136 node.to_in_memory_node(on_disk)?,
137 ))
137 ))
138 })
138 })
139 .collect::<Result<_, _>>()?;
139 .collect::<Result<_, _>>()?;
140 *self = ChildNodes::InMemory(nodes);
140 *self = ChildNodes::InMemory(nodes);
141 match self {
141 match self {
142 ChildNodes::InMemory(nodes) => Ok(nodes),
142 ChildNodes::InMemory(nodes) => Ok(nodes),
143 ChildNodes::OnDisk(_) => unreachable!(),
143 ChildNodes::OnDisk(_) => unreachable!(),
144 }
144 }
145 }
145 }
146 }
146 }
147 }
147 }
148 }
148 }
149
149
150 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
150 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
151 pub(super) fn get(
151 pub(super) fn get(
152 &self,
152 &self,
153 base_name: &HgPath,
153 base_name: &HgPath,
154 on_disk: &'on_disk [u8],
154 on_disk: &'on_disk [u8],
155 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
155 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
156 match self {
156 match self {
157 ChildNodesRef::InMemory(nodes) => Ok(nodes
157 ChildNodesRef::InMemory(nodes) => Ok(nodes
158 .get_key_value(base_name)
158 .get_key_value(base_name)
159 .map(|(k, v)| NodeRef::InMemory(k, v))),
159 .map(|(k, v)| NodeRef::InMemory(k, v))),
160 ChildNodesRef::OnDisk(nodes) => {
160 ChildNodesRef::OnDisk(nodes) => {
161 let mut parse_result = Ok(());
161 let mut parse_result = Ok(());
162 let search_result = nodes.binary_search_by(|node| {
162 let search_result = nodes.binary_search_by(|node| {
163 match node.base_name(on_disk) {
163 match node.base_name(on_disk) {
164 Ok(node_base_name) => node_base_name.cmp(base_name),
164 Ok(node_base_name) => node_base_name.cmp(base_name),
165 Err(e) => {
165 Err(e) => {
166 parse_result = Err(e);
166 parse_result = Err(e);
167 // Dummy comparison result, `search_result` won’t
167 // Dummy comparison result, `search_result` won’t
168 // be used since `parse_result` is an error
168 // be used since `parse_result` is an error
169 std::cmp::Ordering::Equal
169 std::cmp::Ordering::Equal
170 }
170 }
171 }
171 }
172 });
172 });
173 parse_result.map(|()| {
173 parse_result.map(|()| {
174 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
174 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
175 })
175 })
176 }
176 }
177 }
177 }
178 }
178 }
179
179
180 /// Iterate in undefined order
180 /// Iterate in undefined order
181 pub(super) fn iter(
181 pub(super) fn iter(
182 &self,
182 &self,
183 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
183 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
184 match self {
184 match self {
185 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
185 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
186 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
186 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
187 ),
187 ),
188 ChildNodesRef::OnDisk(nodes) => {
188 ChildNodesRef::OnDisk(nodes) => {
189 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
189 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
190 }
190 }
191 }
191 }
192 }
192 }
193
193
194 /// Iterate in parallel in undefined order
194 /// Iterate in parallel in undefined order
195 pub(super) fn par_iter(
195 pub(super) fn par_iter(
196 &self,
196 &self,
197 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
197 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
198 {
198 {
199 use rayon::prelude::*;
199 use rayon::prelude::*;
200 match self {
200 match self {
201 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
201 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
202 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
202 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
203 ),
203 ),
204 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
204 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
205 nodes.par_iter().map(NodeRef::OnDisk),
205 nodes.par_iter().map(NodeRef::OnDisk),
206 ),
206 ),
207 }
207 }
208 }
208 }
209
209
210 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
210 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
211 match self {
211 match self {
212 ChildNodesRef::InMemory(nodes) => {
212 ChildNodesRef::InMemory(nodes) => {
213 let mut vec: Vec<_> = nodes
213 let mut vec: Vec<_> = nodes
214 .iter()
214 .iter()
215 .map(|(k, v)| NodeRef::InMemory(k, v))
215 .map(|(k, v)| NodeRef::InMemory(k, v))
216 .collect();
216 .collect();
217 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
217 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
218 match node {
218 match node {
219 NodeRef::InMemory(path, _node) => path.base_name(),
219 NodeRef::InMemory(path, _node) => path.base_name(),
220 NodeRef::OnDisk(_) => unreachable!(),
220 NodeRef::OnDisk(_) => unreachable!(),
221 }
221 }
222 }
222 }
223 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
223 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
224 // value: https://github.com/rust-lang/rust/issues/34162
224 // value: https://github.com/rust-lang/rust/issues/34162
225 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
225 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
226 vec
226 vec
227 }
227 }
228 ChildNodesRef::OnDisk(nodes) => {
228 ChildNodesRef::OnDisk(nodes) => {
229 // Nodes on disk are already sorted
229 // Nodes on disk are already sorted
230 nodes.iter().map(NodeRef::OnDisk).collect()
230 nodes.iter().map(NodeRef::OnDisk).collect()
231 }
231 }
232 }
232 }
233 }
233 }
234 }
234 }
235
235
236 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
236 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
237 pub(super) fn full_path(
237 pub(super) fn full_path(
238 &self,
238 &self,
239 on_disk: &'on_disk [u8],
239 on_disk: &'on_disk [u8],
240 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
240 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
241 match self {
241 match self {
242 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
242 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
243 NodeRef::OnDisk(node) => node.full_path(on_disk),
243 NodeRef::OnDisk(node) => node.full_path(on_disk),
244 }
244 }
245 }
245 }
246
246
247 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
247 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
248 /// HgPath>` detached from `'tree`
248 /// HgPath>` detached from `'tree`
249 pub(super) fn full_path_borrowed(
249 pub(super) fn full_path_borrowed(
250 &self,
250 &self,
251 on_disk: &'on_disk [u8],
251 on_disk: &'on_disk [u8],
252 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
252 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
253 match self {
253 match self {
254 NodeRef::InMemory(path, _node) => match path.full_path() {
254 NodeRef::InMemory(path, _node) => match path.full_path() {
255 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
255 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
256 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
256 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
257 },
257 },
258 NodeRef::OnDisk(node) => {
258 NodeRef::OnDisk(node) => {
259 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
259 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
260 }
260 }
261 }
261 }
262 }
262 }
263
263
264 pub(super) fn base_name(
264 pub(super) fn base_name(
265 &self,
265 &self,
266 on_disk: &'on_disk [u8],
266 on_disk: &'on_disk [u8],
267 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
267 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
268 match self {
268 match self {
269 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
269 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
270 NodeRef::OnDisk(node) => node.base_name(on_disk),
270 NodeRef::OnDisk(node) => node.base_name(on_disk),
271 }
271 }
272 }
272 }
273
273
274 pub(super) fn children(
274 pub(super) fn children(
275 &self,
275 &self,
276 on_disk: &'on_disk [u8],
276 on_disk: &'on_disk [u8],
277 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
277 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
278 match self {
278 match self {
279 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
279 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
280 NodeRef::OnDisk(node) => {
280 NodeRef::OnDisk(node) => {
281 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
281 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
282 }
282 }
283 }
283 }
284 }
284 }
285
285
286 pub(super) fn has_copy_source(&self) -> bool {
286 pub(super) fn has_copy_source(&self) -> bool {
287 match self {
287 match self {
288 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
288 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
289 NodeRef::OnDisk(node) => node.has_copy_source(),
289 NodeRef::OnDisk(node) => node.has_copy_source(),
290 }
290 }
291 }
291 }
292
292
293 pub(super) fn copy_source(
293 pub(super) fn copy_source(
294 &self,
294 &self,
295 on_disk: &'on_disk [u8],
295 on_disk: &'on_disk [u8],
296 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
296 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
297 match self {
297 match self {
298 NodeRef::InMemory(_path, node) => {
298 NodeRef::InMemory(_path, node) => {
299 Ok(node.copy_source.as_ref().map(|s| &**s))
299 Ok(node.copy_source.as_ref().map(|s| &**s))
300 }
300 }
301 NodeRef::OnDisk(node) => node.copy_source(on_disk),
301 NodeRef::OnDisk(node) => node.copy_source(on_disk),
302 }
302 }
303 }
303 }
304
304
305 pub(super) fn entry(
305 pub(super) fn entry(
306 &self,
306 &self,
307 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
307 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
308 match self {
308 match self {
309 NodeRef::InMemory(_path, node) => {
309 NodeRef::InMemory(_path, node) => {
310 Ok(node.data.as_entry().copied())
310 Ok(node.data.as_entry().copied())
311 }
311 }
312 NodeRef::OnDisk(node) => node.entry(),
312 NodeRef::OnDisk(node) => node.entry(),
313 }
313 }
314 }
314 }
315
315
316 pub(super) fn state(
316 pub(super) fn state(
317 &self,
317 &self,
318 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
318 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
319 match self {
319 match self {
320 NodeRef::InMemory(_path, node) => {
320 NodeRef::InMemory(_path, node) => {
321 Ok(node.data.as_entry().map(|entry| entry.state))
321 Ok(node.data.as_entry().map(|entry| entry.state))
322 }
322 }
323 NodeRef::OnDisk(node) => node.state(),
323 NodeRef::OnDisk(node) => node.state(),
324 }
324 }
325 }
325 }
326
326
327 pub(super) fn cached_directory_mtime(
327 pub(super) fn cached_directory_mtime(
328 &self,
328 &self,
329 ) -> Option<&'tree on_disk::Timestamp> {
329 ) -> Option<&'tree on_disk::Timestamp> {
330 match self {
330 match self {
331 NodeRef::InMemory(_path, node) => match &node.data {
331 NodeRef::InMemory(_path, node) => match &node.data {
332 NodeData::CachedDirectory { mtime } => Some(mtime),
332 NodeData::CachedDirectory { mtime } => Some(mtime),
333 _ => None,
333 _ => None,
334 },
334 },
335 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
335 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
336 }
336 }
337 }
337 }
338
338
339 pub(super) fn descendants_with_entry_count(&self) -> u32 {
339 pub(super) fn descendants_with_entry_count(&self) -> u32 {
340 match self {
340 match self {
341 NodeRef::InMemory(_path, node) => {
341 NodeRef::InMemory(_path, node) => {
342 node.descendants_with_entry_count
342 node.descendants_with_entry_count
343 }
343 }
344 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
344 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
345 }
345 }
346 }
346 }
347
347
348 pub(super) fn tracked_descendants_count(&self) -> u32 {
348 pub(super) fn tracked_descendants_count(&self) -> u32 {
349 match self {
349 match self {
350 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
350 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
351 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
351 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
352 }
352 }
353 }
353 }
354 }
354 }
355
355
356 /// Represents a file or a directory
356 /// Represents a file or a directory
357 #[derive(Default)]
357 #[derive(Default)]
358 pub(super) struct Node<'on_disk> {
358 pub(super) struct Node<'on_disk> {
359 pub(super) data: NodeData,
359 pub(super) data: NodeData,
360
360
361 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
361 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
362
362
363 pub(super) children: ChildNodes<'on_disk>,
363 pub(super) children: ChildNodes<'on_disk>,
364
364
365 /// How many (non-inclusive) descendants of this node have an entry.
365 /// How many (non-inclusive) descendants of this node have an entry.
366 pub(super) descendants_with_entry_count: u32,
366 pub(super) descendants_with_entry_count: u32,
367
367
368 /// How many (non-inclusive) descendants of this node have an entry whose
368 /// How many (non-inclusive) descendants of this node have an entry whose
369 /// state is "tracked".
369 /// state is "tracked".
370 pub(super) tracked_descendants_count: u32,
370 pub(super) tracked_descendants_count: u32,
371 }
371 }
372
372
373 pub(super) enum NodeData {
373 pub(super) enum NodeData {
374 Entry(DirstateEntry),
374 Entry(DirstateEntry),
375 CachedDirectory { mtime: on_disk::Timestamp },
375 CachedDirectory { mtime: on_disk::Timestamp },
376 None,
376 None,
377 }
377 }
378
378
379 impl Default for NodeData {
379 impl Default for NodeData {
380 fn default() -> Self {
380 fn default() -> Self {
381 NodeData::None
381 NodeData::None
382 }
382 }
383 }
383 }
384
384
385 impl NodeData {
385 impl NodeData {
386 fn has_entry(&self) -> bool {
386 fn has_entry(&self) -> bool {
387 match self {
387 match self {
388 NodeData::Entry(_) => true,
388 NodeData::Entry(_) => true,
389 _ => false,
389 _ => false,
390 }
390 }
391 }
391 }
392
392
393 fn as_entry(&self) -> Option<&DirstateEntry> {
393 fn as_entry(&self) -> Option<&DirstateEntry> {
394 match self {
394 match self {
395 NodeData::Entry(entry) => Some(entry),
395 NodeData::Entry(entry) => Some(entry),
396 _ => None,
396 _ => None,
397 }
397 }
398 }
398 }
399 }
399 }
400
400
401 impl<'on_disk> DirstateMap<'on_disk> {
401 impl<'on_disk> DirstateMap<'on_disk> {
402 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
402 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
403 Self {
403 Self {
404 on_disk,
404 on_disk,
405 root: ChildNodes::default(),
405 root: ChildNodes::default(),
406 nodes_with_entry_count: 0,
406 nodes_with_entry_count: 0,
407 nodes_with_copy_source_count: 0,
407 nodes_with_copy_source_count: 0,
408 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
408 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
409 }
409 }
410 }
410 }
411
411
412 #[timed]
412 #[timed]
413 pub fn new_v2(on_disk: &'on_disk [u8]) -> Result<Self, DirstateError> {
413 pub fn new_v2(
414 Ok(on_disk::read(on_disk)?)
414 on_disk: &'on_disk [u8],
415 data_size: usize,
416 ) -> Result<Self, DirstateError> {
417 if let Some(data) = on_disk.get(..data_size) {
418 Ok(on_disk::read(data)?)
419 } else {
420 Err(DirstateV2ParseError.into())
421 }
415 }
422 }
416
423
417 #[timed]
424 #[timed]
418 pub fn new_v1(
425 pub fn new_v1(
419 on_disk: &'on_disk [u8],
426 on_disk: &'on_disk [u8],
420 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
427 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
421 let mut map = Self::empty(on_disk);
428 let mut map = Self::empty(on_disk);
422 if map.on_disk.is_empty() {
429 if map.on_disk.is_empty() {
423 return Ok((map, None));
430 return Ok((map, None));
424 }
431 }
425
432
426 let parents = parse_dirstate_entries(
433 let parents = parse_dirstate_entries(
427 map.on_disk,
434 map.on_disk,
428 |path, entry, copy_source| {
435 |path, entry, copy_source| {
429 let tracked = entry.state.is_tracked();
436 let tracked = entry.state.is_tracked();
430 let node = Self::get_or_insert_node(
437 let node = Self::get_or_insert_node(
431 map.on_disk,
438 map.on_disk,
432 &mut map.root,
439 &mut map.root,
433 path,
440 path,
434 WithBasename::to_cow_borrowed,
441 WithBasename::to_cow_borrowed,
435 |ancestor| {
442 |ancestor| {
436 if tracked {
443 if tracked {
437 ancestor.tracked_descendants_count += 1
444 ancestor.tracked_descendants_count += 1
438 }
445 }
439 ancestor.descendants_with_entry_count += 1
446 ancestor.descendants_with_entry_count += 1
440 },
447 },
441 )?;
448 )?;
442 assert!(
449 assert!(
443 !node.data.has_entry(),
450 !node.data.has_entry(),
444 "duplicate dirstate entry in read"
451 "duplicate dirstate entry in read"
445 );
452 );
446 assert!(
453 assert!(
447 node.copy_source.is_none(),
454 node.copy_source.is_none(),
448 "duplicate dirstate entry in read"
455 "duplicate dirstate entry in read"
449 );
456 );
450 node.data = NodeData::Entry(*entry);
457 node.data = NodeData::Entry(*entry);
451 node.copy_source = copy_source.map(Cow::Borrowed);
458 node.copy_source = copy_source.map(Cow::Borrowed);
452 map.nodes_with_entry_count += 1;
459 map.nodes_with_entry_count += 1;
453 if copy_source.is_some() {
460 if copy_source.is_some() {
454 map.nodes_with_copy_source_count += 1
461 map.nodes_with_copy_source_count += 1
455 }
462 }
456 Ok(())
463 Ok(())
457 },
464 },
458 )?;
465 )?;
459 let parents = Some(parents.clone());
466 let parents = Some(parents.clone());
460
467
461 Ok((map, parents))
468 Ok((map, parents))
462 }
469 }
463
470
464 fn get_node<'tree>(
471 fn get_node<'tree>(
465 &'tree self,
472 &'tree self,
466 path: &HgPath,
473 path: &HgPath,
467 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
474 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
468 let mut children = self.root.as_ref();
475 let mut children = self.root.as_ref();
469 let mut components = path.components();
476 let mut components = path.components();
470 let mut component =
477 let mut component =
471 components.next().expect("expected at least one components");
478 components.next().expect("expected at least one components");
472 loop {
479 loop {
473 if let Some(child) = children.get(component, self.on_disk)? {
480 if let Some(child) = children.get(component, self.on_disk)? {
474 if let Some(next_component) = components.next() {
481 if let Some(next_component) = components.next() {
475 component = next_component;
482 component = next_component;
476 children = child.children(self.on_disk)?;
483 children = child.children(self.on_disk)?;
477 } else {
484 } else {
478 return Ok(Some(child));
485 return Ok(Some(child));
479 }
486 }
480 } else {
487 } else {
481 return Ok(None);
488 return Ok(None);
482 }
489 }
483 }
490 }
484 }
491 }
485
492
486 /// Returns a mutable reference to the node at `path` if it exists
493 /// Returns a mutable reference to the node at `path` if it exists
487 ///
494 ///
488 /// This takes `root` instead of `&mut self` so that callers can mutate
495 /// This takes `root` instead of `&mut self` so that callers can mutate
489 /// other fields while the returned borrow is still valid
496 /// other fields while the returned borrow is still valid
490 fn get_node_mut<'tree>(
497 fn get_node_mut<'tree>(
491 on_disk: &'on_disk [u8],
498 on_disk: &'on_disk [u8],
492 root: &'tree mut ChildNodes<'on_disk>,
499 root: &'tree mut ChildNodes<'on_disk>,
493 path: &HgPath,
500 path: &HgPath,
494 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
501 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
495 let mut children = root;
502 let mut children = root;
496 let mut components = path.components();
503 let mut components = path.components();
497 let mut component =
504 let mut component =
498 components.next().expect("expected at least one components");
505 components.next().expect("expected at least one components");
499 loop {
506 loop {
500 if let Some(child) = children.make_mut(on_disk)?.get_mut(component)
507 if let Some(child) = children.make_mut(on_disk)?.get_mut(component)
501 {
508 {
502 if let Some(next_component) = components.next() {
509 if let Some(next_component) = components.next() {
503 component = next_component;
510 component = next_component;
504 children = &mut child.children;
511 children = &mut child.children;
505 } else {
512 } else {
506 return Ok(Some(child));
513 return Ok(Some(child));
507 }
514 }
508 } else {
515 } else {
509 return Ok(None);
516 return Ok(None);
510 }
517 }
511 }
518 }
512 }
519 }
513
520
514 pub(super) fn get_or_insert<'tree, 'path>(
521 pub(super) fn get_or_insert<'tree, 'path>(
515 &'tree mut self,
522 &'tree mut self,
516 path: &HgPath,
523 path: &HgPath,
517 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
524 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
518 Self::get_or_insert_node(
525 Self::get_or_insert_node(
519 self.on_disk,
526 self.on_disk,
520 &mut self.root,
527 &mut self.root,
521 path,
528 path,
522 WithBasename::to_cow_owned,
529 WithBasename::to_cow_owned,
523 |_| {},
530 |_| {},
524 )
531 )
525 }
532 }
526
533
527 pub(super) fn get_or_insert_node<'tree, 'path>(
534 pub(super) fn get_or_insert_node<'tree, 'path>(
528 on_disk: &'on_disk [u8],
535 on_disk: &'on_disk [u8],
529 root: &'tree mut ChildNodes<'on_disk>,
536 root: &'tree mut ChildNodes<'on_disk>,
530 path: &'path HgPath,
537 path: &'path HgPath,
531 to_cow: impl Fn(
538 to_cow: impl Fn(
532 WithBasename<&'path HgPath>,
539 WithBasename<&'path HgPath>,
533 ) -> WithBasename<Cow<'on_disk, HgPath>>,
540 ) -> WithBasename<Cow<'on_disk, HgPath>>,
534 mut each_ancestor: impl FnMut(&mut Node),
541 mut each_ancestor: impl FnMut(&mut Node),
535 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
542 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
536 let mut child_nodes = root;
543 let mut child_nodes = root;
537 let mut inclusive_ancestor_paths =
544 let mut inclusive_ancestor_paths =
538 WithBasename::inclusive_ancestors_of(path);
545 WithBasename::inclusive_ancestors_of(path);
539 let mut ancestor_path = inclusive_ancestor_paths
546 let mut ancestor_path = inclusive_ancestor_paths
540 .next()
547 .next()
541 .expect("expected at least one inclusive ancestor");
548 .expect("expected at least one inclusive ancestor");
542 loop {
549 loop {
543 // TODO: can we avoid allocating an owned key in cases where the
550 // TODO: can we avoid allocating an owned key in cases where the
544 // map already contains that key, without introducing double
551 // map already contains that key, without introducing double
545 // lookup?
552 // lookup?
546 let child_node = child_nodes
553 let child_node = child_nodes
547 .make_mut(on_disk)?
554 .make_mut(on_disk)?
548 .entry(to_cow(ancestor_path))
555 .entry(to_cow(ancestor_path))
549 .or_default();
556 .or_default();
550 if let Some(next) = inclusive_ancestor_paths.next() {
557 if let Some(next) = inclusive_ancestor_paths.next() {
551 each_ancestor(child_node);
558 each_ancestor(child_node);
552 ancestor_path = next;
559 ancestor_path = next;
553 child_nodes = &mut child_node.children;
560 child_nodes = &mut child_node.children;
554 } else {
561 } else {
555 return Ok(child_node);
562 return Ok(child_node);
556 }
563 }
557 }
564 }
558 }
565 }
559
566
560 fn add_or_remove_file(
567 fn add_or_remove_file(
561 &mut self,
568 &mut self,
562 path: &HgPath,
569 path: &HgPath,
563 old_state: EntryState,
570 old_state: EntryState,
564 new_entry: DirstateEntry,
571 new_entry: DirstateEntry,
565 ) -> Result<(), DirstateV2ParseError> {
572 ) -> Result<(), DirstateV2ParseError> {
566 let had_entry = old_state != EntryState::Unknown;
573 let had_entry = old_state != EntryState::Unknown;
567 let tracked_count_increment =
574 let tracked_count_increment =
568 match (old_state.is_tracked(), new_entry.state.is_tracked()) {
575 match (old_state.is_tracked(), new_entry.state.is_tracked()) {
569 (false, true) => 1,
576 (false, true) => 1,
570 (true, false) => -1,
577 (true, false) => -1,
571 _ => 0,
578 _ => 0,
572 };
579 };
573
580
574 let node = Self::get_or_insert_node(
581 let node = Self::get_or_insert_node(
575 self.on_disk,
582 self.on_disk,
576 &mut self.root,
583 &mut self.root,
577 path,
584 path,
578 WithBasename::to_cow_owned,
585 WithBasename::to_cow_owned,
579 |ancestor| {
586 |ancestor| {
580 if !had_entry {
587 if !had_entry {
581 ancestor.descendants_with_entry_count += 1;
588 ancestor.descendants_with_entry_count += 1;
582 }
589 }
583
590
584 // We can’t use `+= increment` because the counter is unsigned,
591 // We can’t use `+= increment` because the counter is unsigned,
585 // and we want debug builds to detect accidental underflow
592 // and we want debug builds to detect accidental underflow
586 // through zero
593 // through zero
587 match tracked_count_increment {
594 match tracked_count_increment {
588 1 => ancestor.tracked_descendants_count += 1,
595 1 => ancestor.tracked_descendants_count += 1,
589 -1 => ancestor.tracked_descendants_count -= 1,
596 -1 => ancestor.tracked_descendants_count -= 1,
590 _ => {}
597 _ => {}
591 }
598 }
592 },
599 },
593 )?;
600 )?;
594 if !had_entry {
601 if !had_entry {
595 self.nodes_with_entry_count += 1
602 self.nodes_with_entry_count += 1
596 }
603 }
597 node.data = NodeData::Entry(new_entry);
604 node.data = NodeData::Entry(new_entry);
598 Ok(())
605 Ok(())
599 }
606 }
600
607
601 fn iter_nodes<'tree>(
608 fn iter_nodes<'tree>(
602 &'tree self,
609 &'tree self,
603 ) -> impl Iterator<
610 ) -> impl Iterator<
604 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
611 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
605 > + 'tree {
612 > + 'tree {
606 // Depth first tree traversal.
613 // Depth first tree traversal.
607 //
614 //
608 // If we could afford internal iteration and recursion,
615 // If we could afford internal iteration and recursion,
609 // this would look like:
616 // this would look like:
610 //
617 //
611 // ```
618 // ```
612 // fn traverse_children(
619 // fn traverse_children(
613 // children: &ChildNodes,
620 // children: &ChildNodes,
614 // each: &mut impl FnMut(&Node),
621 // each: &mut impl FnMut(&Node),
615 // ) {
622 // ) {
616 // for child in children.values() {
623 // for child in children.values() {
617 // traverse_children(&child.children, each);
624 // traverse_children(&child.children, each);
618 // each(child);
625 // each(child);
619 // }
626 // }
620 // }
627 // }
621 // ```
628 // ```
622 //
629 //
623 // However we want an external iterator and therefore can’t use the
630 // However we want an external iterator and therefore can’t use the
624 // call stack. Use an explicit stack instead:
631 // call stack. Use an explicit stack instead:
625 let mut stack = Vec::new();
632 let mut stack = Vec::new();
626 let mut iter = self.root.as_ref().iter();
633 let mut iter = self.root.as_ref().iter();
627 std::iter::from_fn(move || {
634 std::iter::from_fn(move || {
628 while let Some(child_node) = iter.next() {
635 while let Some(child_node) = iter.next() {
629 let children = match child_node.children(self.on_disk) {
636 let children = match child_node.children(self.on_disk) {
630 Ok(children) => children,
637 Ok(children) => children,
631 Err(error) => return Some(Err(error)),
638 Err(error) => return Some(Err(error)),
632 };
639 };
633 // Pseudo-recursion
640 // Pseudo-recursion
634 let new_iter = children.iter();
641 let new_iter = children.iter();
635 let old_iter = std::mem::replace(&mut iter, new_iter);
642 let old_iter = std::mem::replace(&mut iter, new_iter);
636 stack.push((child_node, old_iter));
643 stack.push((child_node, old_iter));
637 }
644 }
638 // Found the end of a `children.iter()` iterator.
645 // Found the end of a `children.iter()` iterator.
639 if let Some((child_node, next_iter)) = stack.pop() {
646 if let Some((child_node, next_iter)) = stack.pop() {
640 // "Return" from pseudo-recursion by restoring state from the
647 // "Return" from pseudo-recursion by restoring state from the
641 // explicit stack
648 // explicit stack
642 iter = next_iter;
649 iter = next_iter;
643
650
644 Some(Ok(child_node))
651 Some(Ok(child_node))
645 } else {
652 } else {
646 // Reached the bottom of the stack, we’re done
653 // Reached the bottom of the stack, we’re done
647 None
654 None
648 }
655 }
649 })
656 })
650 }
657 }
651
658
652 fn clear_known_ambiguous_mtimes(
659 fn clear_known_ambiguous_mtimes(
653 &mut self,
660 &mut self,
654 paths: &[impl AsRef<HgPath>],
661 paths: &[impl AsRef<HgPath>],
655 ) -> Result<(), DirstateV2ParseError> {
662 ) -> Result<(), DirstateV2ParseError> {
656 for path in paths {
663 for path in paths {
657 if let Some(node) = Self::get_node_mut(
664 if let Some(node) = Self::get_node_mut(
658 self.on_disk,
665 self.on_disk,
659 &mut self.root,
666 &mut self.root,
660 path.as_ref(),
667 path.as_ref(),
661 )? {
668 )? {
662 if let NodeData::Entry(entry) = &mut node.data {
669 if let NodeData::Entry(entry) = &mut node.data {
663 entry.clear_mtime();
670 entry.clear_mtime();
664 }
671 }
665 }
672 }
666 }
673 }
667 Ok(())
674 Ok(())
668 }
675 }
669
676
670 /// Return a faillilble iterator of full paths of nodes that have an
677 /// Return a faillilble iterator of full paths of nodes that have an
671 /// `entry` for which the given `predicate` returns true.
678 /// `entry` for which the given `predicate` returns true.
672 ///
679 ///
673 /// Fallibility means that each iterator item is a `Result`, which may
680 /// Fallibility means that each iterator item is a `Result`, which may
674 /// indicate a parse error of the on-disk dirstate-v2 format. Such errors
681 /// indicate a parse error of the on-disk dirstate-v2 format. Such errors
675 /// should only happen if Mercurial is buggy or a repository is corrupted.
682 /// should only happen if Mercurial is buggy or a repository is corrupted.
676 fn filter_full_paths<'tree>(
683 fn filter_full_paths<'tree>(
677 &'tree self,
684 &'tree self,
678 predicate: impl Fn(&DirstateEntry) -> bool + 'tree,
685 predicate: impl Fn(&DirstateEntry) -> bool + 'tree,
679 ) -> impl Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + 'tree
686 ) -> impl Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + 'tree
680 {
687 {
681 filter_map_results(self.iter_nodes(), move |node| {
688 filter_map_results(self.iter_nodes(), move |node| {
682 if let Some(entry) = node.entry()? {
689 if let Some(entry) = node.entry()? {
683 if predicate(&entry) {
690 if predicate(&entry) {
684 return Ok(Some(node.full_path(self.on_disk)?));
691 return Ok(Some(node.full_path(self.on_disk)?));
685 }
692 }
686 }
693 }
687 Ok(None)
694 Ok(None)
688 })
695 })
689 }
696 }
690 }
697 }
691
698
692 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
699 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
693 ///
700 ///
694 /// The callback is only called for incoming `Ok` values. Errors are passed
701 /// The callback is only called for incoming `Ok` values. Errors are passed
695 /// through as-is. In order to let it use the `?` operator the callback is
702 /// through as-is. In order to let it use the `?` operator the callback is
696 /// expected to return a `Result` of `Option`, instead of an `Option` of
703 /// expected to return a `Result` of `Option`, instead of an `Option` of
697 /// `Result`.
704 /// `Result`.
698 fn filter_map_results<'a, I, F, A, B, E>(
705 fn filter_map_results<'a, I, F, A, B, E>(
699 iter: I,
706 iter: I,
700 f: F,
707 f: F,
701 ) -> impl Iterator<Item = Result<B, E>> + 'a
708 ) -> impl Iterator<Item = Result<B, E>> + 'a
702 where
709 where
703 I: Iterator<Item = Result<A, E>> + 'a,
710 I: Iterator<Item = Result<A, E>> + 'a,
704 F: Fn(A) -> Result<Option<B>, E> + 'a,
711 F: Fn(A) -> Result<Option<B>, E> + 'a,
705 {
712 {
706 iter.filter_map(move |result| match result {
713 iter.filter_map(move |result| match result {
707 Ok(node) => f(node).transpose(),
714 Ok(node) => f(node).transpose(),
708 Err(e) => Some(Err(e)),
715 Err(e) => Some(Err(e)),
709 })
716 })
710 }
717 }
711
718
712 impl<'on_disk> super::dispatch::DirstateMapMethods for DirstateMap<'on_disk> {
719 impl<'on_disk> super::dispatch::DirstateMapMethods for DirstateMap<'on_disk> {
713 fn clear(&mut self) {
720 fn clear(&mut self) {
714 self.root = Default::default();
721 self.root = Default::default();
715 self.nodes_with_entry_count = 0;
722 self.nodes_with_entry_count = 0;
716 self.nodes_with_copy_source_count = 0;
723 self.nodes_with_copy_source_count = 0;
717 }
724 }
718
725
719 fn add_file(
726 fn add_file(
720 &mut self,
727 &mut self,
721 filename: &HgPath,
728 filename: &HgPath,
722 entry: DirstateEntry,
729 entry: DirstateEntry,
723 added: bool,
730 added: bool,
724 merged: bool,
731 merged: bool,
725 from_p2: bool,
732 from_p2: bool,
726 possibly_dirty: bool,
733 possibly_dirty: bool,
727 ) -> Result<(), DirstateError> {
734 ) -> Result<(), DirstateError> {
728 let mut entry = entry;
735 let mut entry = entry;
729 if added {
736 if added {
730 assert!(!possibly_dirty);
737 assert!(!possibly_dirty);
731 assert!(!from_p2);
738 assert!(!from_p2);
732 entry.state = EntryState::Added;
739 entry.state = EntryState::Added;
733 entry.size = SIZE_NON_NORMAL;
740 entry.size = SIZE_NON_NORMAL;
734 entry.mtime = MTIME_UNSET;
741 entry.mtime = MTIME_UNSET;
735 } else if merged {
742 } else if merged {
736 assert!(!possibly_dirty);
743 assert!(!possibly_dirty);
737 assert!(!from_p2);
744 assert!(!from_p2);
738 entry.state = EntryState::Merged;
745 entry.state = EntryState::Merged;
739 entry.size = SIZE_FROM_OTHER_PARENT;
746 entry.size = SIZE_FROM_OTHER_PARENT;
740 entry.mtime = MTIME_UNSET;
747 entry.mtime = MTIME_UNSET;
741 } else if from_p2 {
748 } else if from_p2 {
742 assert!(!possibly_dirty);
749 assert!(!possibly_dirty);
743 entry.state = EntryState::Normal;
750 entry.state = EntryState::Normal;
744 entry.size = SIZE_FROM_OTHER_PARENT;
751 entry.size = SIZE_FROM_OTHER_PARENT;
745 entry.mtime = MTIME_UNSET;
752 entry.mtime = MTIME_UNSET;
746 } else if possibly_dirty {
753 } else if possibly_dirty {
747 entry.state = EntryState::Normal;
754 entry.state = EntryState::Normal;
748 entry.size = SIZE_NON_NORMAL;
755 entry.size = SIZE_NON_NORMAL;
749 entry.mtime = MTIME_UNSET;
756 entry.mtime = MTIME_UNSET;
750 } else {
757 } else {
751 entry.state = EntryState::Normal;
758 entry.state = EntryState::Normal;
752 entry.size = entry.size & V1_RANGEMASK;
759 entry.size = entry.size & V1_RANGEMASK;
753 entry.mtime = entry.mtime & V1_RANGEMASK;
760 entry.mtime = entry.mtime & V1_RANGEMASK;
754 }
761 }
755
762
756 let old_state = match self.get(filename)? {
763 let old_state = match self.get(filename)? {
757 Some(e) => e.state,
764 Some(e) => e.state,
758 None => EntryState::Unknown,
765 None => EntryState::Unknown,
759 };
766 };
760
767
761 Ok(self.add_or_remove_file(filename, old_state, entry)?)
768 Ok(self.add_or_remove_file(filename, old_state, entry)?)
762 }
769 }
763
770
764 fn remove_file(
771 fn remove_file(
765 &mut self,
772 &mut self,
766 filename: &HgPath,
773 filename: &HgPath,
767 in_merge: bool,
774 in_merge: bool,
768 ) -> Result<(), DirstateError> {
775 ) -> Result<(), DirstateError> {
769 let old_entry_opt = self.get(filename)?;
776 let old_entry_opt = self.get(filename)?;
770 let old_state = match old_entry_opt {
777 let old_state = match old_entry_opt {
771 Some(e) => e.state,
778 Some(e) => e.state,
772 None => EntryState::Unknown,
779 None => EntryState::Unknown,
773 };
780 };
774 let mut size = 0;
781 let mut size = 0;
775 if in_merge {
782 if in_merge {
776 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
783 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
777 // during a merge. So I (marmoute) am not sure we need the
784 // during a merge. So I (marmoute) am not sure we need the
778 // conditionnal at all. Adding double checking this with assert
785 // conditionnal at all. Adding double checking this with assert
779 // would be nice.
786 // would be nice.
780 if let Some(old_entry) = old_entry_opt {
787 if let Some(old_entry) = old_entry_opt {
781 // backup the previous state
788 // backup the previous state
782 if old_entry.state == EntryState::Merged {
789 if old_entry.state == EntryState::Merged {
783 size = SIZE_NON_NORMAL;
790 size = SIZE_NON_NORMAL;
784 } else if old_entry.state == EntryState::Normal
791 } else if old_entry.state == EntryState::Normal
785 && old_entry.size == SIZE_FROM_OTHER_PARENT
792 && old_entry.size == SIZE_FROM_OTHER_PARENT
786 {
793 {
787 // other parent
794 // other parent
788 size = SIZE_FROM_OTHER_PARENT;
795 size = SIZE_FROM_OTHER_PARENT;
789 }
796 }
790 }
797 }
791 }
798 }
792 if size == 0 {
799 if size == 0 {
793 self.copy_map_remove(filename)?;
800 self.copy_map_remove(filename)?;
794 }
801 }
795 let entry = DirstateEntry {
802 let entry = DirstateEntry {
796 state: EntryState::Removed,
803 state: EntryState::Removed,
797 mode: 0,
804 mode: 0,
798 size,
805 size,
799 mtime: 0,
806 mtime: 0,
800 };
807 };
801 Ok(self.add_or_remove_file(filename, old_state, entry)?)
808 Ok(self.add_or_remove_file(filename, old_state, entry)?)
802 }
809 }
803
810
804 fn drop_file(&mut self, filename: &HgPath) -> Result<bool, DirstateError> {
811 fn drop_file(&mut self, filename: &HgPath) -> Result<bool, DirstateError> {
805 let old_state = match self.get(filename)? {
812 let old_state = match self.get(filename)? {
806 Some(e) => e.state,
813 Some(e) => e.state,
807 None => EntryState::Unknown,
814 None => EntryState::Unknown,
808 };
815 };
809 struct Dropped {
816 struct Dropped {
810 was_tracked: bool,
817 was_tracked: bool,
811 had_entry: bool,
818 had_entry: bool,
812 had_copy_source: bool,
819 had_copy_source: bool,
813 }
820 }
814
821
815 /// If this returns `Ok(Some((dropped, removed)))`, then
822 /// If this returns `Ok(Some((dropped, removed)))`, then
816 ///
823 ///
817 /// * `dropped` is about the leaf node that was at `filename`
824 /// * `dropped` is about the leaf node that was at `filename`
818 /// * `removed` is whether this particular level of recursion just
825 /// * `removed` is whether this particular level of recursion just
819 /// removed a node in `nodes`.
826 /// removed a node in `nodes`.
820 fn recur<'on_disk>(
827 fn recur<'on_disk>(
821 on_disk: &'on_disk [u8],
828 on_disk: &'on_disk [u8],
822 nodes: &mut ChildNodes<'on_disk>,
829 nodes: &mut ChildNodes<'on_disk>,
823 path: &HgPath,
830 path: &HgPath,
824 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
831 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
825 let (first_path_component, rest_of_path) =
832 let (first_path_component, rest_of_path) =
826 path.split_first_component();
833 path.split_first_component();
827 let node = if let Some(node) =
834 let node = if let Some(node) =
828 nodes.make_mut(on_disk)?.get_mut(first_path_component)
835 nodes.make_mut(on_disk)?.get_mut(first_path_component)
829 {
836 {
830 node
837 node
831 } else {
838 } else {
832 return Ok(None);
839 return Ok(None);
833 };
840 };
834 let dropped;
841 let dropped;
835 if let Some(rest) = rest_of_path {
842 if let Some(rest) = rest_of_path {
836 if let Some((d, removed)) =
843 if let Some((d, removed)) =
837 recur(on_disk, &mut node.children, rest)?
844 recur(on_disk, &mut node.children, rest)?
838 {
845 {
839 dropped = d;
846 dropped = d;
840 if dropped.had_entry {
847 if dropped.had_entry {
841 node.descendants_with_entry_count -= 1;
848 node.descendants_with_entry_count -= 1;
842 }
849 }
843 if dropped.was_tracked {
850 if dropped.was_tracked {
844 node.tracked_descendants_count -= 1;
851 node.tracked_descendants_count -= 1;
845 }
852 }
846
853
847 // Directory caches must be invalidated when removing a
854 // Directory caches must be invalidated when removing a
848 // child node
855 // child node
849 if removed {
856 if removed {
850 if let NodeData::CachedDirectory { .. } = &node.data {
857 if let NodeData::CachedDirectory { .. } = &node.data {
851 node.data = NodeData::None
858 node.data = NodeData::None
852 }
859 }
853 }
860 }
854 } else {
861 } else {
855 return Ok(None);
862 return Ok(None);
856 }
863 }
857 } else {
864 } else {
858 let had_entry = node.data.has_entry();
865 let had_entry = node.data.has_entry();
859 if had_entry {
866 if had_entry {
860 node.data = NodeData::None
867 node.data = NodeData::None
861 }
868 }
862 dropped = Dropped {
869 dropped = Dropped {
863 was_tracked: node
870 was_tracked: node
864 .data
871 .data
865 .as_entry()
872 .as_entry()
866 .map_or(false, |entry| entry.state.is_tracked()),
873 .map_or(false, |entry| entry.state.is_tracked()),
867 had_entry,
874 had_entry,
868 had_copy_source: node.copy_source.take().is_some(),
875 had_copy_source: node.copy_source.take().is_some(),
869 };
876 };
870 }
877 }
871 // After recursion, for both leaf (rest_of_path is None) nodes and
878 // After recursion, for both leaf (rest_of_path is None) nodes and
872 // parent nodes, remove a node if it just became empty.
879 // parent nodes, remove a node if it just became empty.
873 let remove = !node.data.has_entry()
880 let remove = !node.data.has_entry()
874 && node.copy_source.is_none()
881 && node.copy_source.is_none()
875 && node.children.is_empty();
882 && node.children.is_empty();
876 if remove {
883 if remove {
877 nodes.make_mut(on_disk)?.remove(first_path_component);
884 nodes.make_mut(on_disk)?.remove(first_path_component);
878 }
885 }
879 Ok(Some((dropped, remove)))
886 Ok(Some((dropped, remove)))
880 }
887 }
881
888
882 if let Some((dropped, _removed)) =
889 if let Some((dropped, _removed)) =
883 recur(self.on_disk, &mut self.root, filename)?
890 recur(self.on_disk, &mut self.root, filename)?
884 {
891 {
885 if dropped.had_entry {
892 if dropped.had_entry {
886 self.nodes_with_entry_count -= 1
893 self.nodes_with_entry_count -= 1
887 }
894 }
888 if dropped.had_copy_source {
895 if dropped.had_copy_source {
889 self.nodes_with_copy_source_count -= 1
896 self.nodes_with_copy_source_count -= 1
890 }
897 }
891 Ok(dropped.had_entry)
898 Ok(dropped.had_entry)
892 } else {
899 } else {
893 debug_assert!(!old_state.is_tracked());
900 debug_assert!(!old_state.is_tracked());
894 Ok(false)
901 Ok(false)
895 }
902 }
896 }
903 }
897
904
898 fn clear_ambiguous_times(
905 fn clear_ambiguous_times(
899 &mut self,
906 &mut self,
900 filenames: Vec<HgPathBuf>,
907 filenames: Vec<HgPathBuf>,
901 now: i32,
908 now: i32,
902 ) -> Result<(), DirstateV2ParseError> {
909 ) -> Result<(), DirstateV2ParseError> {
903 for filename in filenames {
910 for filename in filenames {
904 if let Some(node) =
911 if let Some(node) =
905 Self::get_node_mut(self.on_disk, &mut self.root, &filename)?
912 Self::get_node_mut(self.on_disk, &mut self.root, &filename)?
906 {
913 {
907 if let NodeData::Entry(entry) = &mut node.data {
914 if let NodeData::Entry(entry) = &mut node.data {
908 entry.clear_ambiguous_mtime(now);
915 entry.clear_ambiguous_mtime(now);
909 }
916 }
910 }
917 }
911 }
918 }
912 Ok(())
919 Ok(())
913 }
920 }
914
921
915 fn non_normal_entries_contains(
922 fn non_normal_entries_contains(
916 &mut self,
923 &mut self,
917 key: &HgPath,
924 key: &HgPath,
918 ) -> Result<bool, DirstateV2ParseError> {
925 ) -> Result<bool, DirstateV2ParseError> {
919 Ok(if let Some(node) = self.get_node(key)? {
926 Ok(if let Some(node) = self.get_node(key)? {
920 node.entry()?.map_or(false, |entry| entry.is_non_normal())
927 node.entry()?.map_or(false, |entry| entry.is_non_normal())
921 } else {
928 } else {
922 false
929 false
923 })
930 })
924 }
931 }
925
932
926 fn non_normal_entries_remove(&mut self, _key: &HgPath) {
933 fn non_normal_entries_remove(&mut self, _key: &HgPath) {
927 // Do nothing, this `DirstateMap` does not have a separate "non normal
934 // Do nothing, this `DirstateMap` does not have a separate "non normal
928 // entries" set that need to be kept up to date
935 // entries" set that need to be kept up to date
929 }
936 }
930
937
931 fn non_normal_or_other_parent_paths(
938 fn non_normal_or_other_parent_paths(
932 &mut self,
939 &mut self,
933 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
940 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
934 {
941 {
935 Box::new(self.filter_full_paths(|entry| {
942 Box::new(self.filter_full_paths(|entry| {
936 entry.is_non_normal() || entry.is_from_other_parent()
943 entry.is_non_normal() || entry.is_from_other_parent()
937 }))
944 }))
938 }
945 }
939
946
940 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
947 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
941 // Do nothing, this `DirstateMap` does not have a separate "non normal
948 // Do nothing, this `DirstateMap` does not have a separate "non normal
942 // entries" and "from other parent" sets that need to be recomputed
949 // entries" and "from other parent" sets that need to be recomputed
943 }
950 }
944
951
945 fn iter_non_normal_paths(
952 fn iter_non_normal_paths(
946 &mut self,
953 &mut self,
947 ) -> Box<
954 ) -> Box<
948 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
955 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
949 > {
956 > {
950 self.iter_non_normal_paths_panic()
957 self.iter_non_normal_paths_panic()
951 }
958 }
952
959
953 fn iter_non_normal_paths_panic(
960 fn iter_non_normal_paths_panic(
954 &self,
961 &self,
955 ) -> Box<
962 ) -> Box<
956 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
963 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
957 > {
964 > {
958 Box::new(self.filter_full_paths(|entry| entry.is_non_normal()))
965 Box::new(self.filter_full_paths(|entry| entry.is_non_normal()))
959 }
966 }
960
967
961 fn iter_other_parent_paths(
968 fn iter_other_parent_paths(
962 &mut self,
969 &mut self,
963 ) -> Box<
970 ) -> Box<
964 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
971 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
965 > {
972 > {
966 Box::new(self.filter_full_paths(|entry| entry.is_from_other_parent()))
973 Box::new(self.filter_full_paths(|entry| entry.is_from_other_parent()))
967 }
974 }
968
975
969 fn has_tracked_dir(
976 fn has_tracked_dir(
970 &mut self,
977 &mut self,
971 directory: &HgPath,
978 directory: &HgPath,
972 ) -> Result<bool, DirstateError> {
979 ) -> Result<bool, DirstateError> {
973 if let Some(node) = self.get_node(directory)? {
980 if let Some(node) = self.get_node(directory)? {
974 // A node without a `DirstateEntry` was created to hold child
981 // A node without a `DirstateEntry` was created to hold child
975 // nodes, and is therefore a directory.
982 // nodes, and is therefore a directory.
976 let state = node.state()?;
983 let state = node.state()?;
977 Ok(state.is_none() && node.tracked_descendants_count() > 0)
984 Ok(state.is_none() && node.tracked_descendants_count() > 0)
978 } else {
985 } else {
979 Ok(false)
986 Ok(false)
980 }
987 }
981 }
988 }
982
989
983 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
990 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
984 if let Some(node) = self.get_node(directory)? {
991 if let Some(node) = self.get_node(directory)? {
985 // A node without a `DirstateEntry` was created to hold child
992 // A node without a `DirstateEntry` was created to hold child
986 // nodes, and is therefore a directory.
993 // nodes, and is therefore a directory.
987 let state = node.state()?;
994 let state = node.state()?;
988 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
995 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
989 } else {
996 } else {
990 Ok(false)
997 Ok(false)
991 }
998 }
992 }
999 }
993
1000
994 #[timed]
1001 #[timed]
995 fn pack_v1(
1002 fn pack_v1(
996 &mut self,
1003 &mut self,
997 parents: DirstateParents,
1004 parents: DirstateParents,
998 now: Timestamp,
1005 now: Timestamp,
999 ) -> Result<Vec<u8>, DirstateError> {
1006 ) -> Result<Vec<u8>, DirstateError> {
1000 let now: i32 = now.0.try_into().expect("time overflow");
1007 let now: i32 = now.0.try_into().expect("time overflow");
1001 let mut ambiguous_mtimes = Vec::new();
1008 let mut ambiguous_mtimes = Vec::new();
1002 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1009 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1003 // reallocations
1010 // reallocations
1004 let mut size = parents.as_bytes().len();
1011 let mut size = parents.as_bytes().len();
1005 for node in self.iter_nodes() {
1012 for node in self.iter_nodes() {
1006 let node = node?;
1013 let node = node?;
1007 if let Some(entry) = node.entry()? {
1014 if let Some(entry) = node.entry()? {
1008 size += packed_entry_size(
1015 size += packed_entry_size(
1009 node.full_path(self.on_disk)?,
1016 node.full_path(self.on_disk)?,
1010 node.copy_source(self.on_disk)?,
1017 node.copy_source(self.on_disk)?,
1011 );
1018 );
1012 if entry.mtime_is_ambiguous(now) {
1019 if entry.mtime_is_ambiguous(now) {
1013 ambiguous_mtimes.push(
1020 ambiguous_mtimes.push(
1014 node.full_path_borrowed(self.on_disk)?
1021 node.full_path_borrowed(self.on_disk)?
1015 .detach_from_tree(),
1022 .detach_from_tree(),
1016 )
1023 )
1017 }
1024 }
1018 }
1025 }
1019 }
1026 }
1020 self.clear_known_ambiguous_mtimes(&ambiguous_mtimes)?;
1027 self.clear_known_ambiguous_mtimes(&ambiguous_mtimes)?;
1021
1028
1022 let mut packed = Vec::with_capacity(size);
1029 let mut packed = Vec::with_capacity(size);
1023 packed.extend(parents.as_bytes());
1030 packed.extend(parents.as_bytes());
1024
1031
1025 for node in self.iter_nodes() {
1032 for node in self.iter_nodes() {
1026 let node = node?;
1033 let node = node?;
1027 if let Some(entry) = node.entry()? {
1034 if let Some(entry) = node.entry()? {
1028 pack_entry(
1035 pack_entry(
1029 node.full_path(self.on_disk)?,
1036 node.full_path(self.on_disk)?,
1030 &entry,
1037 &entry,
1031 node.copy_source(self.on_disk)?,
1038 node.copy_source(self.on_disk)?,
1032 &mut packed,
1039 &mut packed,
1033 );
1040 );
1034 }
1041 }
1035 }
1042 }
1036 Ok(packed)
1043 Ok(packed)
1037 }
1044 }
1038
1045
1039 #[timed]
1046 #[timed]
1040 fn pack_v2(&mut self, now: Timestamp) -> Result<Vec<u8>, DirstateError> {
1047 fn pack_v2(&mut self, now: Timestamp) -> Result<Vec<u8>, DirstateError> {
1041 // TODO:Β how do we want to handle this in 2038?
1048 // TODO:Β how do we want to handle this in 2038?
1042 let now: i32 = now.0.try_into().expect("time overflow");
1049 let now: i32 = now.0.try_into().expect("time overflow");
1043 let mut paths = Vec::new();
1050 let mut paths = Vec::new();
1044 for node in self.iter_nodes() {
1051 for node in self.iter_nodes() {
1045 let node = node?;
1052 let node = node?;
1046 if let Some(entry) = node.entry()? {
1053 if let Some(entry) = node.entry()? {
1047 if entry.mtime_is_ambiguous(now) {
1054 if entry.mtime_is_ambiguous(now) {
1048 paths.push(
1055 paths.push(
1049 node.full_path_borrowed(self.on_disk)?
1056 node.full_path_borrowed(self.on_disk)?
1050 .detach_from_tree(),
1057 .detach_from_tree(),
1051 )
1058 )
1052 }
1059 }
1053 }
1060 }
1054 }
1061 }
1055 // Borrow of `self` ends here since we collect cloned paths
1062 // Borrow of `self` ends here since we collect cloned paths
1056
1063
1057 self.clear_known_ambiguous_mtimes(&paths)?;
1064 self.clear_known_ambiguous_mtimes(&paths)?;
1058
1065
1059 on_disk::write(self)
1066 on_disk::write(self)
1060 }
1067 }
1061
1068
1062 fn status<'a>(
1069 fn status<'a>(
1063 &'a mut self,
1070 &'a mut self,
1064 matcher: &'a (dyn Matcher + Sync),
1071 matcher: &'a (dyn Matcher + Sync),
1065 root_dir: PathBuf,
1072 root_dir: PathBuf,
1066 ignore_files: Vec<PathBuf>,
1073 ignore_files: Vec<PathBuf>,
1067 options: StatusOptions,
1074 options: StatusOptions,
1068 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
1075 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
1069 {
1076 {
1070 super::status::status(self, matcher, root_dir, ignore_files, options)
1077 super::status::status(self, matcher, root_dir, ignore_files, options)
1071 }
1078 }
1072
1079
1073 fn copy_map_len(&self) -> usize {
1080 fn copy_map_len(&self) -> usize {
1074 self.nodes_with_copy_source_count as usize
1081 self.nodes_with_copy_source_count as usize
1075 }
1082 }
1076
1083
1077 fn copy_map_iter(&self) -> CopyMapIter<'_> {
1084 fn copy_map_iter(&self) -> CopyMapIter<'_> {
1078 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1085 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1079 Ok(if let Some(source) = node.copy_source(self.on_disk)? {
1086 Ok(if let Some(source) = node.copy_source(self.on_disk)? {
1080 Some((node.full_path(self.on_disk)?, source))
1087 Some((node.full_path(self.on_disk)?, source))
1081 } else {
1088 } else {
1082 None
1089 None
1083 })
1090 })
1084 }))
1091 }))
1085 }
1092 }
1086
1093
1087 fn copy_map_contains_key(
1094 fn copy_map_contains_key(
1088 &self,
1095 &self,
1089 key: &HgPath,
1096 key: &HgPath,
1090 ) -> Result<bool, DirstateV2ParseError> {
1097 ) -> Result<bool, DirstateV2ParseError> {
1091 Ok(if let Some(node) = self.get_node(key)? {
1098 Ok(if let Some(node) = self.get_node(key)? {
1092 node.has_copy_source()
1099 node.has_copy_source()
1093 } else {
1100 } else {
1094 false
1101 false
1095 })
1102 })
1096 }
1103 }
1097
1104
1098 fn copy_map_get(
1105 fn copy_map_get(
1099 &self,
1106 &self,
1100 key: &HgPath,
1107 key: &HgPath,
1101 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1108 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1102 if let Some(node) = self.get_node(key)? {
1109 if let Some(node) = self.get_node(key)? {
1103 if let Some(source) = node.copy_source(self.on_disk)? {
1110 if let Some(source) = node.copy_source(self.on_disk)? {
1104 return Ok(Some(source));
1111 return Ok(Some(source));
1105 }
1112 }
1106 }
1113 }
1107 Ok(None)
1114 Ok(None)
1108 }
1115 }
1109
1116
1110 fn copy_map_remove(
1117 fn copy_map_remove(
1111 &mut self,
1118 &mut self,
1112 key: &HgPath,
1119 key: &HgPath,
1113 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1120 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1114 let count = &mut self.nodes_with_copy_source_count;
1121 let count = &mut self.nodes_with_copy_source_count;
1115 Ok(
1122 Ok(
1116 Self::get_node_mut(self.on_disk, &mut self.root, key)?.and_then(
1123 Self::get_node_mut(self.on_disk, &mut self.root, key)?.and_then(
1117 |node| {
1124 |node| {
1118 if node.copy_source.is_some() {
1125 if node.copy_source.is_some() {
1119 *count -= 1
1126 *count -= 1
1120 }
1127 }
1121 node.copy_source.take().map(Cow::into_owned)
1128 node.copy_source.take().map(Cow::into_owned)
1122 },
1129 },
1123 ),
1130 ),
1124 )
1131 )
1125 }
1132 }
1126
1133
1127 fn copy_map_insert(
1134 fn copy_map_insert(
1128 &mut self,
1135 &mut self,
1129 key: HgPathBuf,
1136 key: HgPathBuf,
1130 value: HgPathBuf,
1137 value: HgPathBuf,
1131 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1138 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1132 let node = Self::get_or_insert_node(
1139 let node = Self::get_or_insert_node(
1133 self.on_disk,
1140 self.on_disk,
1134 &mut self.root,
1141 &mut self.root,
1135 &key,
1142 &key,
1136 WithBasename::to_cow_owned,
1143 WithBasename::to_cow_owned,
1137 |_ancestor| {},
1144 |_ancestor| {},
1138 )?;
1145 )?;
1139 if node.copy_source.is_none() {
1146 if node.copy_source.is_none() {
1140 self.nodes_with_copy_source_count += 1
1147 self.nodes_with_copy_source_count += 1
1141 }
1148 }
1142 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1149 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1143 }
1150 }
1144
1151
1145 fn len(&self) -> usize {
1152 fn len(&self) -> usize {
1146 self.nodes_with_entry_count as usize
1153 self.nodes_with_entry_count as usize
1147 }
1154 }
1148
1155
1149 fn contains_key(
1156 fn contains_key(
1150 &self,
1157 &self,
1151 key: &HgPath,
1158 key: &HgPath,
1152 ) -> Result<bool, DirstateV2ParseError> {
1159 ) -> Result<bool, DirstateV2ParseError> {
1153 Ok(self.get(key)?.is_some())
1160 Ok(self.get(key)?.is_some())
1154 }
1161 }
1155
1162
1156 fn get(
1163 fn get(
1157 &self,
1164 &self,
1158 key: &HgPath,
1165 key: &HgPath,
1159 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1166 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1160 Ok(if let Some(node) = self.get_node(key)? {
1167 Ok(if let Some(node) = self.get_node(key)? {
1161 node.entry()?
1168 node.entry()?
1162 } else {
1169 } else {
1163 None
1170 None
1164 })
1171 })
1165 }
1172 }
1166
1173
1167 fn iter(&self) -> StateMapIter<'_> {
1174 fn iter(&self) -> StateMapIter<'_> {
1168 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1175 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1169 Ok(if let Some(entry) = node.entry()? {
1176 Ok(if let Some(entry) = node.entry()? {
1170 Some((node.full_path(self.on_disk)?, entry))
1177 Some((node.full_path(self.on_disk)?, entry))
1171 } else {
1178 } else {
1172 None
1179 None
1173 })
1180 })
1174 }))
1181 }))
1175 }
1182 }
1176
1183
1177 fn iter_directories(
1184 fn iter_directories(
1178 &self,
1185 &self,
1179 ) -> Box<
1186 ) -> Box<
1180 dyn Iterator<
1187 dyn Iterator<
1181 Item = Result<
1188 Item = Result<
1182 (&HgPath, Option<Timestamp>),
1189 (&HgPath, Option<Timestamp>),
1183 DirstateV2ParseError,
1190 DirstateV2ParseError,
1184 >,
1191 >,
1185 > + Send
1192 > + Send
1186 + '_,
1193 + '_,
1187 > {
1194 > {
1188 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1195 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1189 Ok(if node.state()?.is_none() {
1196 Ok(if node.state()?.is_none() {
1190 Some((
1197 Some((
1191 node.full_path(self.on_disk)?,
1198 node.full_path(self.on_disk)?,
1192 node.cached_directory_mtime()
1199 node.cached_directory_mtime()
1193 .map(|mtime| Timestamp(mtime.seconds())),
1200 .map(|mtime| Timestamp(mtime.seconds())),
1194 ))
1201 ))
1195 } else {
1202 } else {
1196 None
1203 None
1197 })
1204 })
1198 }))
1205 }))
1199 }
1206 }
1200 }
1207 }
@@ -1,636 +1,641 b''
1 //! The "version 2" disk representation of the dirstate
1 //! The "version 2" disk representation of the dirstate
2 //!
2 //!
3 //! # File format
3 //! # File format
4 //!
4 //!
5 //! The file starts with a fixed-sized header, whose layout is defined by the
5 //! The file starts with a fixed-sized header, whose layout is defined by the
6 //! `Header` struct. Its `root` field contains the slice (offset and length) to
6 //! `Header` struct. Its `root` field contains the slice (offset and length) to
7 //! the nodes representing the files and directories at the root of the
7 //! the nodes representing the files and directories at the root of the
8 //! repository. Each node is also fixed-size, defined by the `Node` struct.
8 //! repository. Each node is also fixed-size, defined by the `Node` struct.
9 //! Nodes in turn contain slices to variable-size paths, and to their own child
9 //! Nodes in turn contain slices to variable-size paths, and to their own child
10 //! nodes (if any) for nested files and directories.
10 //! nodes (if any) for nested files and directories.
11
11
12 use crate::dirstate_tree::dirstate_map::{self, DirstateMap, NodeRef};
12 use crate::dirstate_tree::dirstate_map::{self, DirstateMap, NodeRef};
13 use crate::dirstate_tree::path_with_basename::WithBasename;
13 use crate::dirstate_tree::path_with_basename::WithBasename;
14 use crate::errors::HgError;
14 use crate::errors::HgError;
15 use crate::utils::hg_path::HgPath;
15 use crate::utils::hg_path::HgPath;
16 use crate::DirstateEntry;
16 use crate::DirstateEntry;
17 use crate::DirstateError;
17 use crate::DirstateError;
18 use crate::DirstateParents;
18 use crate::DirstateParents;
19 use crate::EntryState;
19 use crate::EntryState;
20 use bytes_cast::unaligned::{I32Be, I64Be, U32Be};
20 use bytes_cast::unaligned::{I32Be, I64Be, U32Be};
21 use bytes_cast::BytesCast;
21 use bytes_cast::BytesCast;
22 use format_bytes::format_bytes;
22 use format_bytes::format_bytes;
23 use std::borrow::Cow;
23 use std::borrow::Cow;
24 use std::convert::TryFrom;
24 use std::convert::{TryFrom, TryInto};
25 use std::time::{Duration, SystemTime, UNIX_EPOCH};
25 use std::time::{Duration, SystemTime, UNIX_EPOCH};
26
26
27 /// Added at the start of `.hg/dirstate` when the "v2" format is used.
27 /// Added at the start of `.hg/dirstate` when the "v2" format is used.
28 /// This a redundant sanity check more than an actual "magic number" since
28 /// This a redundant sanity check more than an actual "magic number" since
29 /// `.hg/requires` already governs which format should be used.
29 /// `.hg/requires` already governs which format should be used.
30 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
30 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
31
31
32 /// Keep space for 256-bit hashes
32 /// Keep space for 256-bit hashes
33 const STORED_NODE_ID_BYTES: usize = 32;
33 const STORED_NODE_ID_BYTES: usize = 32;
34
34
35 /// … even though only 160 bits are used for now, with SHA-1
35 /// … even though only 160 bits are used for now, with SHA-1
36 const USED_NODE_ID_BYTES: usize = 20;
36 const USED_NODE_ID_BYTES: usize = 20;
37
37
38 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
38 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
39 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
39 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
40
40
41 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
41 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
42 #[derive(BytesCast)]
42 #[derive(BytesCast)]
43 #[repr(C)]
43 #[repr(C)]
44 struct DocketHeader {
44 struct DocketHeader {
45 marker: [u8; V2_FORMAT_MARKER.len()],
45 marker: [u8; V2_FORMAT_MARKER.len()],
46 parent_1: [u8; STORED_NODE_ID_BYTES],
46 parent_1: [u8; STORED_NODE_ID_BYTES],
47 parent_2: [u8; STORED_NODE_ID_BYTES],
47 parent_2: [u8; STORED_NODE_ID_BYTES],
48 data_size: Size,
48 data_size: Size,
49 uuid_size: u8,
49 uuid_size: u8,
50 }
50 }
51
51
52 pub struct Docket<'on_disk> {
52 pub struct Docket<'on_disk> {
53 header: &'on_disk DocketHeader,
53 header: &'on_disk DocketHeader,
54 uuid: &'on_disk [u8],
54 uuid: &'on_disk [u8],
55 }
55 }
56
56
57 #[derive(BytesCast)]
57 #[derive(BytesCast)]
58 #[repr(C)]
58 #[repr(C)]
59 struct Header {
59 struct Header {
60 root: ChildNodes,
60 root: ChildNodes,
61 nodes_with_entry_count: Size,
61 nodes_with_entry_count: Size,
62 nodes_with_copy_source_count: Size,
62 nodes_with_copy_source_count: Size,
63
63
64 /// If non-zero, a hash of ignore files that were used for some previous
64 /// If non-zero, a hash of ignore files that were used for some previous
65 /// run of the `status` algorithm.
65 /// run of the `status` algorithm.
66 ///
66 ///
67 /// We define:
67 /// We define:
68 ///
68 ///
69 /// * "Root" ignore files are `.hgignore` at the root of the repository if
69 /// * "Root" ignore files are `.hgignore` at the root of the repository if
70 /// it exists, and files from `ui.ignore.*` config. This set of files is
70 /// it exists, and files from `ui.ignore.*` config. This set of files is
71 /// then sorted by the string representation of their path.
71 /// then sorted by the string representation of their path.
72 /// * The "expanded contents" of an ignore files is the byte string made
72 /// * The "expanded contents" of an ignore files is the byte string made
73 /// by concatenating its contents with the "expanded contents" of other
73 /// by concatenating its contents with the "expanded contents" of other
74 /// files included with `include:` or `subinclude:` files, in inclusion
74 /// files included with `include:` or `subinclude:` files, in inclusion
75 /// order. This definition is recursive, as included files can
75 /// order. This definition is recursive, as included files can
76 /// themselves include more files.
76 /// themselves include more files.
77 ///
77 ///
78 /// This hash is defined as the SHA-1 of the concatenation (in sorted
78 /// This hash is defined as the SHA-1 of the concatenation (in sorted
79 /// order) of the "expanded contents" of each "root" ignore file.
79 /// order) of the "expanded contents" of each "root" ignore file.
80 /// (Note that computing this does not require actually concatenating byte
80 /// (Note that computing this does not require actually concatenating byte
81 /// strings into contiguous memory, instead SHA-1 hashing can be done
81 /// strings into contiguous memory, instead SHA-1 hashing can be done
82 /// incrementally.)
82 /// incrementally.)
83 ignore_patterns_hash: IgnorePatternsHash,
83 ignore_patterns_hash: IgnorePatternsHash,
84 }
84 }
85
85
86 #[derive(BytesCast)]
86 #[derive(BytesCast)]
87 #[repr(C)]
87 #[repr(C)]
88 pub(super) struct Node {
88 pub(super) struct Node {
89 full_path: PathSlice,
89 full_path: PathSlice,
90
90
91 /// In bytes from `self.full_path.start`
91 /// In bytes from `self.full_path.start`
92 base_name_start: Size,
92 base_name_start: Size,
93
93
94 copy_source: OptPathSlice,
94 copy_source: OptPathSlice,
95 children: ChildNodes,
95 children: ChildNodes,
96 pub(super) descendants_with_entry_count: Size,
96 pub(super) descendants_with_entry_count: Size,
97 pub(super) tracked_descendants_count: Size,
97 pub(super) tracked_descendants_count: Size,
98
98
99 /// Depending on the value of `state`:
99 /// Depending on the value of `state`:
100 ///
100 ///
101 /// * A null byte: `data` is not used.
101 /// * A null byte: `data` is not used.
102 ///
102 ///
103 /// * A `n`, `a`, `r`, or `m` ASCII byte: `state` and `data` together
103 /// * A `n`, `a`, `r`, or `m` ASCII byte: `state` and `data` together
104 /// represent a dirstate entry like in the v1 format.
104 /// represent a dirstate entry like in the v1 format.
105 ///
105 ///
106 /// * A `d` ASCII byte: the bytes of `data` should instead be interpreted
106 /// * A `d` ASCII byte: the bytes of `data` should instead be interpreted
107 /// as the `Timestamp` for the mtime of a cached directory.
107 /// as the `Timestamp` for the mtime of a cached directory.
108 ///
108 ///
109 /// The presence of this state means that at some point, this path in
109 /// The presence of this state means that at some point, this path in
110 /// the working directory was observed:
110 /// the working directory was observed:
111 ///
111 ///
112 /// - To be a directory
112 /// - To be a directory
113 /// - With the modification time as given by `Timestamp`
113 /// - With the modification time as given by `Timestamp`
114 /// - That timestamp was already strictly in the past when observed,
114 /// - That timestamp was already strictly in the past when observed,
115 /// meaning that later changes cannot happen in the same clock tick
115 /// meaning that later changes cannot happen in the same clock tick
116 /// and must cause a different modification time (unless the system
116 /// and must cause a different modification time (unless the system
117 /// clock jumps back and we get unlucky, which is not impossible but
117 /// clock jumps back and we get unlucky, which is not impossible but
118 /// but deemed unlikely enough).
118 /// but deemed unlikely enough).
119 /// - All direct children of this directory (as returned by
119 /// - All direct children of this directory (as returned by
120 /// `std::fs::read_dir`) either have a corresponding dirstate node, or
120 /// `std::fs::read_dir`) either have a corresponding dirstate node, or
121 /// are ignored by ignore patterns whose hash is in
121 /// are ignored by ignore patterns whose hash is in
122 /// `Header::ignore_patterns_hash`.
122 /// `Header::ignore_patterns_hash`.
123 ///
123 ///
124 /// This means that if `std::fs::symlink_metadata` later reports the
124 /// This means that if `std::fs::symlink_metadata` later reports the
125 /// same modification time and ignored patterns haven’t changed, a run
125 /// same modification time and ignored patterns haven’t changed, a run
126 /// of status that is not listing ignored files can skip calling
126 /// of status that is not listing ignored files can skip calling
127 /// `std::fs::read_dir` again for this directory, iterate child
127 /// `std::fs::read_dir` again for this directory, iterate child
128 /// dirstate nodes instead.
128 /// dirstate nodes instead.
129 state: u8,
129 state: u8,
130 data: Entry,
130 data: Entry,
131 }
131 }
132
132
133 #[derive(BytesCast, Copy, Clone)]
133 #[derive(BytesCast, Copy, Clone)]
134 #[repr(C)]
134 #[repr(C)]
135 struct Entry {
135 struct Entry {
136 mode: I32Be,
136 mode: I32Be,
137 mtime: I32Be,
137 mtime: I32Be,
138 size: I32Be,
138 size: I32Be,
139 }
139 }
140
140
141 /// Duration since the Unix epoch
141 /// Duration since the Unix epoch
142 #[derive(BytesCast, Copy, Clone, PartialEq)]
142 #[derive(BytesCast, Copy, Clone, PartialEq)]
143 #[repr(C)]
143 #[repr(C)]
144 pub(super) struct Timestamp {
144 pub(super) struct Timestamp {
145 seconds: I64Be,
145 seconds: I64Be,
146
146
147 /// In `0 .. 1_000_000_000`.
147 /// In `0 .. 1_000_000_000`.
148 ///
148 ///
149 /// This timestamp is later or earlier than `(seconds, 0)` by this many
149 /// This timestamp is later or earlier than `(seconds, 0)` by this many
150 /// nanoseconds, if `seconds` is non-negative or negative, respectively.
150 /// nanoseconds, if `seconds` is non-negative or negative, respectively.
151 nanoseconds: U32Be,
151 nanoseconds: U32Be,
152 }
152 }
153
153
154 /// Counted in bytes from the start of the file
154 /// Counted in bytes from the start of the file
155 ///
155 ///
156 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
156 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
157 type Offset = U32Be;
157 type Offset = U32Be;
158
158
159 /// Counted in number of items
159 /// Counted in number of items
160 ///
160 ///
161 /// NOTE: not supporting directories with more than 4 billion direct children,
161 /// NOTE: not supporting directories with more than 4 billion direct children,
162 /// or filenames more than 4 GiB.
162 /// or filenames more than 4 GiB.
163 type Size = U32Be;
163 type Size = U32Be;
164
164
165 /// Location of consecutive, fixed-size items.
165 /// Location of consecutive, fixed-size items.
166 ///
166 ///
167 /// An item can be a single byte for paths, or a struct with
167 /// An item can be a single byte for paths, or a struct with
168 /// `derive(BytesCast)`.
168 /// `derive(BytesCast)`.
169 #[derive(BytesCast, Copy, Clone)]
169 #[derive(BytesCast, Copy, Clone)]
170 #[repr(C)]
170 #[repr(C)]
171 struct Slice {
171 struct Slice {
172 start: Offset,
172 start: Offset,
173 len: Size,
173 len: Size,
174 }
174 }
175
175
176 /// A contiguous sequence of `len` times `Node`, representing the child nodes
176 /// A contiguous sequence of `len` times `Node`, representing the child nodes
177 /// of either some other node or of the repository root.
177 /// of either some other node or of the repository root.
178 ///
178 ///
179 /// Always sorted by ascending `full_path`, to allow binary search.
179 /// Always sorted by ascending `full_path`, to allow binary search.
180 /// Since nodes with the same parent nodes also have the same parent path,
180 /// Since nodes with the same parent nodes also have the same parent path,
181 /// only the `base_name`s need to be compared during binary search.
181 /// only the `base_name`s need to be compared during binary search.
182 type ChildNodes = Slice;
182 type ChildNodes = Slice;
183
183
184 /// A `HgPath` of `len` bytes
184 /// A `HgPath` of `len` bytes
185 type PathSlice = Slice;
185 type PathSlice = Slice;
186
186
187 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
187 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
188 type OptPathSlice = Slice;
188 type OptPathSlice = Slice;
189
189
190 /// Make sure that size-affecting changes are made knowingly
190 /// Make sure that size-affecting changes are made knowingly
191 fn _static_assert_size_of() {
191 fn _static_assert_size_of() {
192 let _ = std::mem::transmute::<DocketHeader, [u8; 81]>;
192 let _ = std::mem::transmute::<DocketHeader, [u8; 81]>;
193 let _ = std::mem::transmute::<Header, [u8; 36]>;
193 let _ = std::mem::transmute::<Header, [u8; 36]>;
194 let _ = std::mem::transmute::<Node, [u8; 49]>;
194 let _ = std::mem::transmute::<Node, [u8; 49]>;
195 }
195 }
196
196
197 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
197 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
198 ///
198 ///
199 /// This should only happen if Mercurial is buggy or a repository is corrupted.
199 /// This should only happen if Mercurial is buggy or a repository is corrupted.
200 #[derive(Debug)]
200 #[derive(Debug)]
201 pub struct DirstateV2ParseError;
201 pub struct DirstateV2ParseError;
202
202
203 impl From<DirstateV2ParseError> for HgError {
203 impl From<DirstateV2ParseError> for HgError {
204 fn from(_: DirstateV2ParseError) -> Self {
204 fn from(_: DirstateV2ParseError) -> Self {
205 HgError::corrupted("dirstate-v2 parse error")
205 HgError::corrupted("dirstate-v2 parse error")
206 }
206 }
207 }
207 }
208
208
209 impl From<DirstateV2ParseError> for crate::DirstateError {
209 impl From<DirstateV2ParseError> for crate::DirstateError {
210 fn from(error: DirstateV2ParseError) -> Self {
210 fn from(error: DirstateV2ParseError) -> Self {
211 HgError::from(error).into()
211 HgError::from(error).into()
212 }
212 }
213 }
213 }
214
214
215 impl<'on_disk> Docket<'on_disk> {
215 impl<'on_disk> Docket<'on_disk> {
216 pub fn parents(&self) -> DirstateParents {
216 pub fn parents(&self) -> DirstateParents {
217 use crate::Node;
217 use crate::Node;
218 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
218 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
219 .unwrap()
219 .unwrap()
220 .clone();
220 .clone();
221 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
221 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
222 .unwrap()
222 .unwrap()
223 .clone();
223 .clone();
224 DirstateParents { p1, p2 }
224 DirstateParents { p1, p2 }
225 }
225 }
226
226
227 pub fn data_size(&self) -> usize {
228 // This `unwrap` could only panic on a 16-bit CPU
229 self.header.data_size.get().try_into().unwrap()
230 }
231
227 pub fn data_filename(&self) -> String {
232 pub fn data_filename(&self) -> String {
228 String::from_utf8(format_bytes!(b"dirstate.{}.d", self.uuid)).unwrap()
233 String::from_utf8(format_bytes!(b"dirstate.{}.d", self.uuid)).unwrap()
229 }
234 }
230 }
235 }
231
236
232 pub fn read_docket(
237 pub fn read_docket(
233 on_disk: &[u8],
238 on_disk: &[u8],
234 ) -> Result<Docket<'_>, DirstateV2ParseError> {
239 ) -> Result<Docket<'_>, DirstateV2ParseError> {
235 let (header, uuid) =
240 let (header, uuid) =
236 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
241 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
237 let uuid_size = header.uuid_size as usize;
242 let uuid_size = header.uuid_size as usize;
238 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
243 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
239 Ok(Docket { header, uuid })
244 Ok(Docket { header, uuid })
240 } else {
245 } else {
241 Err(DirstateV2ParseError)
246 Err(DirstateV2ParseError)
242 }
247 }
243 }
248 }
244
249
245 pub(super) fn read<'on_disk>(
250 pub(super) fn read<'on_disk>(
246 on_disk: &'on_disk [u8],
251 on_disk: &'on_disk [u8],
247 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
252 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
248 if on_disk.is_empty() {
253 if on_disk.is_empty() {
249 return Ok(DirstateMap::empty(on_disk));
254 return Ok(DirstateMap::empty(on_disk));
250 }
255 }
251 let (header, _) =
256 let (header, _) =
252 Header::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
257 Header::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
253 let dirstate_map = DirstateMap {
258 let dirstate_map = DirstateMap {
254 on_disk,
259 on_disk,
255 root: dirstate_map::ChildNodes::OnDisk(read_slice::<Node>(
260 root: dirstate_map::ChildNodes::OnDisk(read_slice::<Node>(
256 on_disk,
261 on_disk,
257 header.root,
262 header.root,
258 )?),
263 )?),
259 nodes_with_entry_count: header.nodes_with_entry_count.get(),
264 nodes_with_entry_count: header.nodes_with_entry_count.get(),
260 nodes_with_copy_source_count: header
265 nodes_with_copy_source_count: header
261 .nodes_with_copy_source_count
266 .nodes_with_copy_source_count
262 .get(),
267 .get(),
263 ignore_patterns_hash: header.ignore_patterns_hash,
268 ignore_patterns_hash: header.ignore_patterns_hash,
264 };
269 };
265 Ok(dirstate_map)
270 Ok(dirstate_map)
266 }
271 }
267
272
268 impl Node {
273 impl Node {
269 pub(super) fn full_path<'on_disk>(
274 pub(super) fn full_path<'on_disk>(
270 &self,
275 &self,
271 on_disk: &'on_disk [u8],
276 on_disk: &'on_disk [u8],
272 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
277 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
273 read_hg_path(on_disk, self.full_path)
278 read_hg_path(on_disk, self.full_path)
274 }
279 }
275
280
276 pub(super) fn base_name_start<'on_disk>(
281 pub(super) fn base_name_start<'on_disk>(
277 &self,
282 &self,
278 ) -> Result<usize, DirstateV2ParseError> {
283 ) -> Result<usize, DirstateV2ParseError> {
279 let start = self.base_name_start.get();
284 let start = self.base_name_start.get();
280 if start < self.full_path.len.get() {
285 if start < self.full_path.len.get() {
281 let start = usize::try_from(start)
286 let start = usize::try_from(start)
282 // u32 -> usize, could only panic on a 16-bit CPU
287 // u32 -> usize, could only panic on a 16-bit CPU
283 .expect("dirstate-v2 base_name_start out of bounds");
288 .expect("dirstate-v2 base_name_start out of bounds");
284 Ok(start)
289 Ok(start)
285 } else {
290 } else {
286 Err(DirstateV2ParseError)
291 Err(DirstateV2ParseError)
287 }
292 }
288 }
293 }
289
294
290 pub(super) fn base_name<'on_disk>(
295 pub(super) fn base_name<'on_disk>(
291 &self,
296 &self,
292 on_disk: &'on_disk [u8],
297 on_disk: &'on_disk [u8],
293 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
298 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
294 let full_path = self.full_path(on_disk)?;
299 let full_path = self.full_path(on_disk)?;
295 let base_name_start = self.base_name_start()?;
300 let base_name_start = self.base_name_start()?;
296 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
301 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
297 }
302 }
298
303
299 pub(super) fn path<'on_disk>(
304 pub(super) fn path<'on_disk>(
300 &self,
305 &self,
301 on_disk: &'on_disk [u8],
306 on_disk: &'on_disk [u8],
302 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
307 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
303 Ok(WithBasename::from_raw_parts(
308 Ok(WithBasename::from_raw_parts(
304 Cow::Borrowed(self.full_path(on_disk)?),
309 Cow::Borrowed(self.full_path(on_disk)?),
305 self.base_name_start()?,
310 self.base_name_start()?,
306 ))
311 ))
307 }
312 }
308
313
309 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
314 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
310 self.copy_source.start.get() != 0
315 self.copy_source.start.get() != 0
311 }
316 }
312
317
313 pub(super) fn copy_source<'on_disk>(
318 pub(super) fn copy_source<'on_disk>(
314 &self,
319 &self,
315 on_disk: &'on_disk [u8],
320 on_disk: &'on_disk [u8],
316 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
321 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
317 Ok(if self.has_copy_source() {
322 Ok(if self.has_copy_source() {
318 Some(read_hg_path(on_disk, self.copy_source)?)
323 Some(read_hg_path(on_disk, self.copy_source)?)
319 } else {
324 } else {
320 None
325 None
321 })
326 })
322 }
327 }
323
328
324 pub(super) fn node_data(
329 pub(super) fn node_data(
325 &self,
330 &self,
326 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
331 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
327 let entry = |state| {
332 let entry = |state| {
328 dirstate_map::NodeData::Entry(self.entry_with_given_state(state))
333 dirstate_map::NodeData::Entry(self.entry_with_given_state(state))
329 };
334 };
330
335
331 match self.state {
336 match self.state {
332 b'\0' => Ok(dirstate_map::NodeData::None),
337 b'\0' => Ok(dirstate_map::NodeData::None),
333 b'd' => Ok(dirstate_map::NodeData::CachedDirectory {
338 b'd' => Ok(dirstate_map::NodeData::CachedDirectory {
334 mtime: *self.data.as_timestamp(),
339 mtime: *self.data.as_timestamp(),
335 }),
340 }),
336 b'n' => Ok(entry(EntryState::Normal)),
341 b'n' => Ok(entry(EntryState::Normal)),
337 b'a' => Ok(entry(EntryState::Added)),
342 b'a' => Ok(entry(EntryState::Added)),
338 b'r' => Ok(entry(EntryState::Removed)),
343 b'r' => Ok(entry(EntryState::Removed)),
339 b'm' => Ok(entry(EntryState::Merged)),
344 b'm' => Ok(entry(EntryState::Merged)),
340 _ => Err(DirstateV2ParseError),
345 _ => Err(DirstateV2ParseError),
341 }
346 }
342 }
347 }
343
348
344 pub(super) fn cached_directory_mtime(&self) -> Option<&Timestamp> {
349 pub(super) fn cached_directory_mtime(&self) -> Option<&Timestamp> {
345 if self.state == b'd' {
350 if self.state == b'd' {
346 Some(self.data.as_timestamp())
351 Some(self.data.as_timestamp())
347 } else {
352 } else {
348 None
353 None
349 }
354 }
350 }
355 }
351
356
352 pub(super) fn state(
357 pub(super) fn state(
353 &self,
358 &self,
354 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
359 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
355 match self.state {
360 match self.state {
356 b'\0' | b'd' => Ok(None),
361 b'\0' | b'd' => Ok(None),
357 b'n' => Ok(Some(EntryState::Normal)),
362 b'n' => Ok(Some(EntryState::Normal)),
358 b'a' => Ok(Some(EntryState::Added)),
363 b'a' => Ok(Some(EntryState::Added)),
359 b'r' => Ok(Some(EntryState::Removed)),
364 b'r' => Ok(Some(EntryState::Removed)),
360 b'm' => Ok(Some(EntryState::Merged)),
365 b'm' => Ok(Some(EntryState::Merged)),
361 _ => Err(DirstateV2ParseError),
366 _ => Err(DirstateV2ParseError),
362 }
367 }
363 }
368 }
364
369
365 fn entry_with_given_state(&self, state: EntryState) -> DirstateEntry {
370 fn entry_with_given_state(&self, state: EntryState) -> DirstateEntry {
366 DirstateEntry {
371 DirstateEntry {
367 state,
372 state,
368 mode: self.data.mode.get(),
373 mode: self.data.mode.get(),
369 mtime: self.data.mtime.get(),
374 mtime: self.data.mtime.get(),
370 size: self.data.size.get(),
375 size: self.data.size.get(),
371 }
376 }
372 }
377 }
373
378
374 pub(super) fn entry(
379 pub(super) fn entry(
375 &self,
380 &self,
376 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
381 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
377 Ok(self
382 Ok(self
378 .state()?
383 .state()?
379 .map(|state| self.entry_with_given_state(state)))
384 .map(|state| self.entry_with_given_state(state)))
380 }
385 }
381
386
382 pub(super) fn children<'on_disk>(
387 pub(super) fn children<'on_disk>(
383 &self,
388 &self,
384 on_disk: &'on_disk [u8],
389 on_disk: &'on_disk [u8],
385 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
390 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
386 read_slice::<Node>(on_disk, self.children)
391 read_slice::<Node>(on_disk, self.children)
387 }
392 }
388
393
389 pub(super) fn to_in_memory_node<'on_disk>(
394 pub(super) fn to_in_memory_node<'on_disk>(
390 &self,
395 &self,
391 on_disk: &'on_disk [u8],
396 on_disk: &'on_disk [u8],
392 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
397 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
393 Ok(dirstate_map::Node {
398 Ok(dirstate_map::Node {
394 children: dirstate_map::ChildNodes::OnDisk(
399 children: dirstate_map::ChildNodes::OnDisk(
395 self.children(on_disk)?,
400 self.children(on_disk)?,
396 ),
401 ),
397 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
402 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
398 data: self.node_data()?,
403 data: self.node_data()?,
399 descendants_with_entry_count: self
404 descendants_with_entry_count: self
400 .descendants_with_entry_count
405 .descendants_with_entry_count
401 .get(),
406 .get(),
402 tracked_descendants_count: self.tracked_descendants_count.get(),
407 tracked_descendants_count: self.tracked_descendants_count.get(),
403 })
408 })
404 }
409 }
405 }
410 }
406
411
407 impl Entry {
412 impl Entry {
408 fn from_timestamp(timestamp: Timestamp) -> Self {
413 fn from_timestamp(timestamp: Timestamp) -> Self {
409 // Safety: both types implement the `ByteCast` trait, so we could
414 // Safety: both types implement the `ByteCast` trait, so we could
410 // safely use `as_bytes` and `from_bytes` to do this conversion. Using
415 // safely use `as_bytes` and `from_bytes` to do this conversion. Using
411 // `transmute` instead makes the compiler check that the two types
416 // `transmute` instead makes the compiler check that the two types
412 // have the same size, which eliminates the error case of
417 // have the same size, which eliminates the error case of
413 // `from_bytes`.
418 // `from_bytes`.
414 unsafe { std::mem::transmute::<Timestamp, Entry>(timestamp) }
419 unsafe { std::mem::transmute::<Timestamp, Entry>(timestamp) }
415 }
420 }
416
421
417 fn as_timestamp(&self) -> &Timestamp {
422 fn as_timestamp(&self) -> &Timestamp {
418 // Safety: same as above in `from_timestamp`
423 // Safety: same as above in `from_timestamp`
419 unsafe { &*(self as *const Entry as *const Timestamp) }
424 unsafe { &*(self as *const Entry as *const Timestamp) }
420 }
425 }
421 }
426 }
422
427
423 impl Timestamp {
428 impl Timestamp {
424 pub fn seconds(&self) -> i64 {
429 pub fn seconds(&self) -> i64 {
425 self.seconds.get()
430 self.seconds.get()
426 }
431 }
427 }
432 }
428
433
429 impl From<SystemTime> for Timestamp {
434 impl From<SystemTime> for Timestamp {
430 fn from(system_time: SystemTime) -> Self {
435 fn from(system_time: SystemTime) -> Self {
431 let (secs, nanos) = match system_time.duration_since(UNIX_EPOCH) {
436 let (secs, nanos) = match system_time.duration_since(UNIX_EPOCH) {
432 Ok(duration) => {
437 Ok(duration) => {
433 (duration.as_secs() as i64, duration.subsec_nanos())
438 (duration.as_secs() as i64, duration.subsec_nanos())
434 }
439 }
435 Err(error) => {
440 Err(error) => {
436 let negative = error.duration();
441 let negative = error.duration();
437 (-(negative.as_secs() as i64), negative.subsec_nanos())
442 (-(negative.as_secs() as i64), negative.subsec_nanos())
438 }
443 }
439 };
444 };
440 Timestamp {
445 Timestamp {
441 seconds: secs.into(),
446 seconds: secs.into(),
442 nanoseconds: nanos.into(),
447 nanoseconds: nanos.into(),
443 }
448 }
444 }
449 }
445 }
450 }
446
451
447 impl From<&'_ Timestamp> for SystemTime {
452 impl From<&'_ Timestamp> for SystemTime {
448 fn from(timestamp: &'_ Timestamp) -> Self {
453 fn from(timestamp: &'_ Timestamp) -> Self {
449 let secs = timestamp.seconds.get();
454 let secs = timestamp.seconds.get();
450 let nanos = timestamp.nanoseconds.get();
455 let nanos = timestamp.nanoseconds.get();
451 if secs >= 0 {
456 if secs >= 0 {
452 UNIX_EPOCH + Duration::new(secs as u64, nanos)
457 UNIX_EPOCH + Duration::new(secs as u64, nanos)
453 } else {
458 } else {
454 UNIX_EPOCH - Duration::new((-secs) as u64, nanos)
459 UNIX_EPOCH - Duration::new((-secs) as u64, nanos)
455 }
460 }
456 }
461 }
457 }
462 }
458
463
459 fn read_hg_path(
464 fn read_hg_path(
460 on_disk: &[u8],
465 on_disk: &[u8],
461 slice: Slice,
466 slice: Slice,
462 ) -> Result<&HgPath, DirstateV2ParseError> {
467 ) -> Result<&HgPath, DirstateV2ParseError> {
463 let bytes = read_slice::<u8>(on_disk, slice)?;
468 let bytes = read_slice::<u8>(on_disk, slice)?;
464 Ok(HgPath::new(bytes))
469 Ok(HgPath::new(bytes))
465 }
470 }
466
471
467 fn read_slice<T>(
472 fn read_slice<T>(
468 on_disk: &[u8],
473 on_disk: &[u8],
469 slice: Slice,
474 slice: Slice,
470 ) -> Result<&[T], DirstateV2ParseError>
475 ) -> Result<&[T], DirstateV2ParseError>
471 where
476 where
472 T: BytesCast,
477 T: BytesCast,
473 {
478 {
474 // Either `usize::MAX` would result in "out of bounds" error since a single
479 // Either `usize::MAX` would result in "out of bounds" error since a single
475 // `&[u8]` cannot occupy the entire addess space.
480 // `&[u8]` cannot occupy the entire addess space.
476 let start = usize::try_from(slice.start.get()).unwrap_or(std::usize::MAX);
481 let start = usize::try_from(slice.start.get()).unwrap_or(std::usize::MAX);
477 let len = usize::try_from(slice.len.get()).unwrap_or(std::usize::MAX);
482 let len = usize::try_from(slice.len.get()).unwrap_or(std::usize::MAX);
478 on_disk
483 on_disk
479 .get(start..)
484 .get(start..)
480 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
485 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
481 .map(|(slice, _rest)| slice)
486 .map(|(slice, _rest)| slice)
482 .ok_or_else(|| DirstateV2ParseError)
487 .ok_or_else(|| DirstateV2ParseError)
483 }
488 }
484
489
485 pub(crate) fn for_each_tracked_path<'on_disk>(
490 pub(crate) fn for_each_tracked_path<'on_disk>(
486 on_disk: &'on_disk [u8],
491 on_disk: &'on_disk [u8],
487 mut f: impl FnMut(&'on_disk HgPath),
492 mut f: impl FnMut(&'on_disk HgPath),
488 ) -> Result<(), DirstateV2ParseError> {
493 ) -> Result<(), DirstateV2ParseError> {
489 let (header, _) =
494 let (header, _) =
490 Header::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
495 Header::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
491 fn recur<'on_disk>(
496 fn recur<'on_disk>(
492 on_disk: &'on_disk [u8],
497 on_disk: &'on_disk [u8],
493 nodes: Slice,
498 nodes: Slice,
494 f: &mut impl FnMut(&'on_disk HgPath),
499 f: &mut impl FnMut(&'on_disk HgPath),
495 ) -> Result<(), DirstateV2ParseError> {
500 ) -> Result<(), DirstateV2ParseError> {
496 for node in read_slice::<Node>(on_disk, nodes)? {
501 for node in read_slice::<Node>(on_disk, nodes)? {
497 if let Some(state) = node.state()? {
502 if let Some(state) = node.state()? {
498 if state.is_tracked() {
503 if state.is_tracked() {
499 f(node.full_path(on_disk)?)
504 f(node.full_path(on_disk)?)
500 }
505 }
501 }
506 }
502 recur(on_disk, node.children, f)?
507 recur(on_disk, node.children, f)?
503 }
508 }
504 Ok(())
509 Ok(())
505 }
510 }
506 recur(on_disk, header.root, &mut f)
511 recur(on_disk, header.root, &mut f)
507 }
512 }
508
513
509 pub(super) fn write(
514 pub(super) fn write(
510 dirstate_map: &mut DirstateMap,
515 dirstate_map: &mut DirstateMap,
511 ) -> Result<Vec<u8>, DirstateError> {
516 ) -> Result<Vec<u8>, DirstateError> {
512 let header_len = std::mem::size_of::<Header>();
517 let header_len = std::mem::size_of::<Header>();
513
518
514 // This ignores the space for paths, and for nodes without an entry.
519 // This ignores the space for paths, and for nodes without an entry.
515 // TODO: better estimate? Skip the `Vec` and write to a file directly?
520 // TODO: better estimate? Skip the `Vec` and write to a file directly?
516 let size_guess = header_len
521 let size_guess = header_len
517 + std::mem::size_of::<Node>()
522 + std::mem::size_of::<Node>()
518 * dirstate_map.nodes_with_entry_count as usize;
523 * dirstate_map.nodes_with_entry_count as usize;
519 let mut out = Vec::with_capacity(size_guess);
524 let mut out = Vec::with_capacity(size_guess);
520
525
521 // Keep space for the header. We’ll fill it out at the end when we know the
526 // Keep space for the header. We’ll fill it out at the end when we know the
522 // actual offset for the root nodes.
527 // actual offset for the root nodes.
523 out.resize(header_len, 0_u8);
528 out.resize(header_len, 0_u8);
524
529
525 let root =
530 let root =
526 write_nodes(dirstate_map, dirstate_map.root.as_ref(), &mut out)?;
531 write_nodes(dirstate_map, dirstate_map.root.as_ref(), &mut out)?;
527
532
528 let header = Header {
533 let header = Header {
529 root,
534 root,
530 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
535 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
531 nodes_with_copy_source_count: dirstate_map
536 nodes_with_copy_source_count: dirstate_map
532 .nodes_with_copy_source_count
537 .nodes_with_copy_source_count
533 .into(),
538 .into(),
534 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
539 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
535 };
540 };
536 out[..header_len].copy_from_slice(header.as_bytes());
541 out[..header_len].copy_from_slice(header.as_bytes());
537 Ok(out)
542 Ok(out)
538 }
543 }
539
544
540 fn write_nodes(
545 fn write_nodes(
541 dirstate_map: &DirstateMap,
546 dirstate_map: &DirstateMap,
542 nodes: dirstate_map::ChildNodesRef,
547 nodes: dirstate_map::ChildNodesRef,
543 out: &mut Vec<u8>,
548 out: &mut Vec<u8>,
544 ) -> Result<ChildNodes, DirstateError> {
549 ) -> Result<ChildNodes, DirstateError> {
545 // `dirstate_map::ChildNodes` is a `HashMap` with undefined iteration
550 // `dirstate_map::ChildNodes` is a `HashMap` with undefined iteration
546 // order. Sort to enable binary search in the written file.
551 // order. Sort to enable binary search in the written file.
547 let nodes = nodes.sorted();
552 let nodes = nodes.sorted();
548
553
549 // First accumulate serialized nodes in a `Vec`
554 // First accumulate serialized nodes in a `Vec`
550 let mut on_disk_nodes = Vec::with_capacity(nodes.len());
555 let mut on_disk_nodes = Vec::with_capacity(nodes.len());
551 for node in nodes {
556 for node in nodes {
552 let children = write_nodes(
557 let children = write_nodes(
553 dirstate_map,
558 dirstate_map,
554 node.children(dirstate_map.on_disk)?,
559 node.children(dirstate_map.on_disk)?,
555 out,
560 out,
556 )?;
561 )?;
557 let full_path = node.full_path(dirstate_map.on_disk)?;
562 let full_path = node.full_path(dirstate_map.on_disk)?;
558 let full_path = write_slice::<u8>(full_path.as_bytes(), out);
563 let full_path = write_slice::<u8>(full_path.as_bytes(), out);
559 let copy_source =
564 let copy_source =
560 if let Some(source) = node.copy_source(dirstate_map.on_disk)? {
565 if let Some(source) = node.copy_source(dirstate_map.on_disk)? {
561 write_slice::<u8>(source.as_bytes(), out)
566 write_slice::<u8>(source.as_bytes(), out)
562 } else {
567 } else {
563 Slice {
568 Slice {
564 start: 0.into(),
569 start: 0.into(),
565 len: 0.into(),
570 len: 0.into(),
566 }
571 }
567 };
572 };
568 on_disk_nodes.push(match node {
573 on_disk_nodes.push(match node {
569 NodeRef::InMemory(path, node) => {
574 NodeRef::InMemory(path, node) => {
570 let (state, data) = match &node.data {
575 let (state, data) = match &node.data {
571 dirstate_map::NodeData::Entry(entry) => (
576 dirstate_map::NodeData::Entry(entry) => (
572 entry.state.into(),
577 entry.state.into(),
573 Entry {
578 Entry {
574 mode: entry.mode.into(),
579 mode: entry.mode.into(),
575 mtime: entry.mtime.into(),
580 mtime: entry.mtime.into(),
576 size: entry.size.into(),
581 size: entry.size.into(),
577 },
582 },
578 ),
583 ),
579 dirstate_map::NodeData::CachedDirectory { mtime } => {
584 dirstate_map::NodeData::CachedDirectory { mtime } => {
580 (b'd', Entry::from_timestamp(*mtime))
585 (b'd', Entry::from_timestamp(*mtime))
581 }
586 }
582 dirstate_map::NodeData::None => (
587 dirstate_map::NodeData::None => (
583 b'\0',
588 b'\0',
584 Entry {
589 Entry {
585 mode: 0.into(),
590 mode: 0.into(),
586 mtime: 0.into(),
591 mtime: 0.into(),
587 size: 0.into(),
592 size: 0.into(),
588 },
593 },
589 ),
594 ),
590 };
595 };
591 Node {
596 Node {
592 children,
597 children,
593 copy_source,
598 copy_source,
594 full_path,
599 full_path,
595 base_name_start: u32::try_from(path.base_name_start())
600 base_name_start: u32::try_from(path.base_name_start())
596 // Could only panic for paths over 4 GiB
601 // Could only panic for paths over 4 GiB
597 .expect("dirstate-v2 offset overflow")
602 .expect("dirstate-v2 offset overflow")
598 .into(),
603 .into(),
599 descendants_with_entry_count: node
604 descendants_with_entry_count: node
600 .descendants_with_entry_count
605 .descendants_with_entry_count
601 .into(),
606 .into(),
602 tracked_descendants_count: node
607 tracked_descendants_count: node
603 .tracked_descendants_count
608 .tracked_descendants_count
604 .into(),
609 .into(),
605 state,
610 state,
606 data,
611 data,
607 }
612 }
608 }
613 }
609 NodeRef::OnDisk(node) => Node {
614 NodeRef::OnDisk(node) => Node {
610 children,
615 children,
611 copy_source,
616 copy_source,
612 full_path,
617 full_path,
613 ..*node
618 ..*node
614 },
619 },
615 })
620 })
616 }
621 }
617 // … so we can write them contiguously
622 // … so we can write them contiguously
618 Ok(write_slice::<Node>(&on_disk_nodes, out))
623 Ok(write_slice::<Node>(&on_disk_nodes, out))
619 }
624 }
620
625
621 fn write_slice<T>(slice: &[T], out: &mut Vec<u8>) -> Slice
626 fn write_slice<T>(slice: &[T], out: &mut Vec<u8>) -> Slice
622 where
627 where
623 T: BytesCast,
628 T: BytesCast,
624 {
629 {
625 let start = u32::try_from(out.len())
630 let start = u32::try_from(out.len())
626 // Could only panic for a dirstate file larger than 4 GiB
631 // Could only panic for a dirstate file larger than 4 GiB
627 .expect("dirstate-v2 offset overflow")
632 .expect("dirstate-v2 offset overflow")
628 .into();
633 .into();
629 let len = u32::try_from(slice.len())
634 let len = u32::try_from(slice.len())
630 // Could only panic for paths over 4 GiB or nodes with over 4 billions
635 // Could only panic for paths over 4 GiB or nodes with over 4 billions
631 // child nodes
636 // child nodes
632 .expect("dirstate-v2 offset overflow")
637 .expect("dirstate-v2 offset overflow")
633 .into();
638 .into();
634 out.extend(slice.as_bytes());
639 out.extend(slice.as_bytes());
635 Slice { start, len }
640 Slice { start, len }
636 }
641 }
@@ -1,606 +1,607 b''
1 // dirstate_map.rs
1 // dirstate_map.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 //! Bindings for the `hg::dirstate::dirstate_map` file provided by the
8 //! Bindings for the `hg::dirstate::dirstate_map` file provided by the
9 //! `hg-core` package.
9 //! `hg-core` package.
10
10
11 use std::cell::{RefCell, RefMut};
11 use std::cell::{RefCell, RefMut};
12 use std::convert::TryInto;
12 use std::convert::TryInto;
13
13
14 use cpython::{
14 use cpython::{
15 exc, ObjectProtocol, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList,
15 exc, ObjectProtocol, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList,
16 PyObject, PyResult, PySet, PyString, Python, PythonObject, ToPyObject,
16 PyObject, PyResult, PySet, PyString, Python, PythonObject, ToPyObject,
17 UnsafePyLeaked,
17 UnsafePyLeaked,
18 };
18 };
19
19
20 use crate::{
20 use crate::{
21 dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
21 dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
22 dirstate::make_directory_item,
22 dirstate::make_directory_item,
23 dirstate::make_dirstate_item,
23 dirstate::make_dirstate_item,
24 dirstate::non_normal_entries::{
24 dirstate::non_normal_entries::{
25 NonNormalEntries, NonNormalEntriesIterator,
25 NonNormalEntries, NonNormalEntriesIterator,
26 },
26 },
27 dirstate::owning::OwningDirstateMap,
27 dirstate::owning::OwningDirstateMap,
28 parsers::dirstate_parents_to_pytuple,
28 parsers::dirstate_parents_to_pytuple,
29 };
29 };
30 use hg::{
30 use hg::{
31 dirstate::parsers::Timestamp,
31 dirstate::parsers::Timestamp,
32 dirstate::MTIME_UNSET,
32 dirstate::MTIME_UNSET,
33 dirstate::SIZE_NON_NORMAL,
33 dirstate::SIZE_NON_NORMAL,
34 dirstate_tree::dispatch::DirstateMapMethods,
34 dirstate_tree::dispatch::DirstateMapMethods,
35 dirstate_tree::on_disk::DirstateV2ParseError,
35 dirstate_tree::on_disk::DirstateV2ParseError,
36 revlog::Node,
36 revlog::Node,
37 utils::files::normalize_case,
37 utils::files::normalize_case,
38 utils::hg_path::{HgPath, HgPathBuf},
38 utils::hg_path::{HgPath, HgPathBuf},
39 DirstateEntry, DirstateError, DirstateMap as RustDirstateMap,
39 DirstateEntry, DirstateError, DirstateMap as RustDirstateMap,
40 DirstateParents, EntryState, StateMapIter,
40 DirstateParents, EntryState, StateMapIter,
41 };
41 };
42
42
43 // TODO
43 // TODO
44 // This object needs to share references to multiple members of its Rust
44 // This object needs to share references to multiple members of its Rust
45 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
45 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
46 // Right now `CopyMap` is done, but it needs to have an explicit reference
46 // Right now `CopyMap` is done, but it needs to have an explicit reference
47 // to `RustDirstateMap` which itself needs to have an encapsulation for
47 // to `RustDirstateMap` which itself needs to have an encapsulation for
48 // every method in `CopyMap` (copymapcopy, etc.).
48 // every method in `CopyMap` (copymapcopy, etc.).
49 // This is ugly and hard to maintain.
49 // This is ugly and hard to maintain.
50 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
50 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
51 // `py_class!` is already implemented and does not mention
51 // `py_class!` is already implemented and does not mention
52 // `RustDirstateMap`, rightfully so.
52 // `RustDirstateMap`, rightfully so.
53 // All attributes also have to have a separate refcount data attribute for
53 // All attributes also have to have a separate refcount data attribute for
54 // leaks, with all methods that go along for reference sharing.
54 // leaks, with all methods that go along for reference sharing.
55 py_class!(pub class DirstateMap |py| {
55 py_class!(pub class DirstateMap |py| {
56 @shared data inner: Box<dyn DirstateMapMethods + Send>;
56 @shared data inner: Box<dyn DirstateMapMethods + Send>;
57
57
58 /// Returns a `(dirstate_map, parents)` tuple
58 /// Returns a `(dirstate_map, parents)` tuple
59 @staticmethod
59 @staticmethod
60 def new_v1(
60 def new_v1(
61 use_dirstate_tree: bool,
61 use_dirstate_tree: bool,
62 on_disk: PyBytes,
62 on_disk: PyBytes,
63 ) -> PyResult<PyObject> {
63 ) -> PyResult<PyObject> {
64 let dirstate_error = |e: DirstateError| {
64 let dirstate_error = |e: DirstateError| {
65 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
65 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
66 };
66 };
67 let (inner, parents) = if use_dirstate_tree {
67 let (inner, parents) = if use_dirstate_tree {
68 let (map, parents) = OwningDirstateMap::new_v1(py, on_disk)
68 let (map, parents) = OwningDirstateMap::new_v1(py, on_disk)
69 .map_err(dirstate_error)?;
69 .map_err(dirstate_error)?;
70 (Box::new(map) as _, parents)
70 (Box::new(map) as _, parents)
71 } else {
71 } else {
72 let bytes = on_disk.data(py);
72 let bytes = on_disk.data(py);
73 let mut map = RustDirstateMap::default();
73 let mut map = RustDirstateMap::default();
74 let parents = map.read(bytes).map_err(dirstate_error)?;
74 let parents = map.read(bytes).map_err(dirstate_error)?;
75 (Box::new(map) as _, parents)
75 (Box::new(map) as _, parents)
76 };
76 };
77 let map = Self::create_instance(py, inner)?;
77 let map = Self::create_instance(py, inner)?;
78 let parents = parents.map(|p| dirstate_parents_to_pytuple(py, &p));
78 let parents = parents.map(|p| dirstate_parents_to_pytuple(py, &p));
79 Ok((map, parents).to_py_object(py).into_object())
79 Ok((map, parents).to_py_object(py).into_object())
80 }
80 }
81
81
82 /// Returns a DirstateMap
82 /// Returns a DirstateMap
83 @staticmethod
83 @staticmethod
84 def new_v2(
84 def new_v2(
85 on_disk: PyBytes,
85 on_disk: PyBytes,
86 data_size: usize,
86 ) -> PyResult<PyObject> {
87 ) -> PyResult<PyObject> {
87 let dirstate_error = |e: DirstateError| {
88 let dirstate_error = |e: DirstateError| {
88 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
89 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
89 };
90 };
90 let inner = OwningDirstateMap::new_v2(py, on_disk)
91 let inner = OwningDirstateMap::new_v2(py, on_disk, data_size)
91 .map_err(dirstate_error)?;
92 .map_err(dirstate_error)?;
92 let map = Self::create_instance(py, Box::new(inner))?;
93 let map = Self::create_instance(py, Box::new(inner))?;
93 Ok(map.into_object())
94 Ok(map.into_object())
94 }
95 }
95
96
96 def clear(&self) -> PyResult<PyObject> {
97 def clear(&self) -> PyResult<PyObject> {
97 self.inner(py).borrow_mut().clear();
98 self.inner(py).borrow_mut().clear();
98 Ok(py.None())
99 Ok(py.None())
99 }
100 }
100
101
101 def get(
102 def get(
102 &self,
103 &self,
103 key: PyObject,
104 key: PyObject,
104 default: Option<PyObject> = None
105 default: Option<PyObject> = None
105 ) -> PyResult<Option<PyObject>> {
106 ) -> PyResult<Option<PyObject>> {
106 let key = key.extract::<PyBytes>(py)?;
107 let key = key.extract::<PyBytes>(py)?;
107 match self
108 match self
108 .inner(py)
109 .inner(py)
109 .borrow()
110 .borrow()
110 .get(HgPath::new(key.data(py)))
111 .get(HgPath::new(key.data(py)))
111 .map_err(|e| v2_error(py, e))?
112 .map_err(|e| v2_error(py, e))?
112 {
113 {
113 Some(entry) => {
114 Some(entry) => {
114 Ok(Some(make_dirstate_item(py, &entry)?))
115 Ok(Some(make_dirstate_item(py, &entry)?))
115 },
116 },
116 None => Ok(default)
117 None => Ok(default)
117 }
118 }
118 }
119 }
119
120
120 def addfile(
121 def addfile(
121 &self,
122 &self,
122 f: PyObject,
123 f: PyObject,
123 mode: PyObject,
124 mode: PyObject,
124 size: PyObject,
125 size: PyObject,
125 mtime: PyObject,
126 mtime: PyObject,
126 added: PyObject,
127 added: PyObject,
127 merged: PyObject,
128 merged: PyObject,
128 from_p2: PyObject,
129 from_p2: PyObject,
129 possibly_dirty: PyObject,
130 possibly_dirty: PyObject,
130 ) -> PyResult<PyObject> {
131 ) -> PyResult<PyObject> {
131 let f = f.extract::<PyBytes>(py)?;
132 let f = f.extract::<PyBytes>(py)?;
132 let filename = HgPath::new(f.data(py));
133 let filename = HgPath::new(f.data(py));
133 let mode = if mode.is_none(py) {
134 let mode = if mode.is_none(py) {
134 // fallback default value
135 // fallback default value
135 0
136 0
136 } else {
137 } else {
137 mode.extract(py)?
138 mode.extract(py)?
138 };
139 };
139 let size = if size.is_none(py) {
140 let size = if size.is_none(py) {
140 // fallback default value
141 // fallback default value
141 SIZE_NON_NORMAL
142 SIZE_NON_NORMAL
142 } else {
143 } else {
143 size.extract(py)?
144 size.extract(py)?
144 };
145 };
145 let mtime = if mtime.is_none(py) {
146 let mtime = if mtime.is_none(py) {
146 // fallback default value
147 // fallback default value
147 MTIME_UNSET
148 MTIME_UNSET
148 } else {
149 } else {
149 mtime.extract(py)?
150 mtime.extract(py)?
150 };
151 };
151 let entry = DirstateEntry {
152 let entry = DirstateEntry {
152 // XXX Arbitrary default value since the value is determined later
153 // XXX Arbitrary default value since the value is determined later
153 state: EntryState::Normal,
154 state: EntryState::Normal,
154 mode: mode,
155 mode: mode,
155 size: size,
156 size: size,
156 mtime: mtime,
157 mtime: mtime,
157 };
158 };
158 let added = added.extract::<PyBool>(py)?.is_true();
159 let added = added.extract::<PyBool>(py)?.is_true();
159 let merged = merged.extract::<PyBool>(py)?.is_true();
160 let merged = merged.extract::<PyBool>(py)?.is_true();
160 let from_p2 = from_p2.extract::<PyBool>(py)?.is_true();
161 let from_p2 = from_p2.extract::<PyBool>(py)?.is_true();
161 let possibly_dirty = possibly_dirty.extract::<PyBool>(py)?.is_true();
162 let possibly_dirty = possibly_dirty.extract::<PyBool>(py)?.is_true();
162 self.inner(py).borrow_mut().add_file(
163 self.inner(py).borrow_mut().add_file(
163 filename,
164 filename,
164 entry,
165 entry,
165 added,
166 added,
166 merged,
167 merged,
167 from_p2,
168 from_p2,
168 possibly_dirty
169 possibly_dirty
169 ).and(Ok(py.None())).or_else(|e: DirstateError| {
170 ).and(Ok(py.None())).or_else(|e: DirstateError| {
170 Err(PyErr::new::<exc::ValueError, _>(py, e.to_string()))
171 Err(PyErr::new::<exc::ValueError, _>(py, e.to_string()))
171 })
172 })
172 }
173 }
173
174
174 def removefile(
175 def removefile(
175 &self,
176 &self,
176 f: PyObject,
177 f: PyObject,
177 in_merge: PyObject
178 in_merge: PyObject
178 ) -> PyResult<PyObject> {
179 ) -> PyResult<PyObject> {
179 self.inner(py).borrow_mut()
180 self.inner(py).borrow_mut()
180 .remove_file(
181 .remove_file(
181 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
182 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
182 in_merge.extract::<PyBool>(py)?.is_true(),
183 in_merge.extract::<PyBool>(py)?.is_true(),
183 )
184 )
184 .or_else(|_| {
185 .or_else(|_| {
185 Err(PyErr::new::<exc::OSError, _>(
186 Err(PyErr::new::<exc::OSError, _>(
186 py,
187 py,
187 "Dirstate error".to_string(),
188 "Dirstate error".to_string(),
188 ))
189 ))
189 })?;
190 })?;
190 Ok(py.None())
191 Ok(py.None())
191 }
192 }
192
193
193 def dropfile(
194 def dropfile(
194 &self,
195 &self,
195 f: PyObject,
196 f: PyObject,
196 ) -> PyResult<PyBool> {
197 ) -> PyResult<PyBool> {
197 self.inner(py).borrow_mut()
198 self.inner(py).borrow_mut()
198 .drop_file(
199 .drop_file(
199 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
200 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
200 )
201 )
201 .and_then(|b| Ok(b.to_py_object(py)))
202 .and_then(|b| Ok(b.to_py_object(py)))
202 .or_else(|e| {
203 .or_else(|e| {
203 Err(PyErr::new::<exc::OSError, _>(
204 Err(PyErr::new::<exc::OSError, _>(
204 py,
205 py,
205 format!("Dirstate error: {}", e.to_string()),
206 format!("Dirstate error: {}", e.to_string()),
206 ))
207 ))
207 })
208 })
208 }
209 }
209
210
210 def clearambiguoustimes(
211 def clearambiguoustimes(
211 &self,
212 &self,
212 files: PyObject,
213 files: PyObject,
213 now: PyObject
214 now: PyObject
214 ) -> PyResult<PyObject> {
215 ) -> PyResult<PyObject> {
215 let files: PyResult<Vec<HgPathBuf>> = files
216 let files: PyResult<Vec<HgPathBuf>> = files
216 .iter(py)?
217 .iter(py)?
217 .map(|filename| {
218 .map(|filename| {
218 Ok(HgPathBuf::from_bytes(
219 Ok(HgPathBuf::from_bytes(
219 filename?.extract::<PyBytes>(py)?.data(py),
220 filename?.extract::<PyBytes>(py)?.data(py),
220 ))
221 ))
221 })
222 })
222 .collect();
223 .collect();
223 self.inner(py)
224 self.inner(py)
224 .borrow_mut()
225 .borrow_mut()
225 .clear_ambiguous_times(files?, now.extract(py)?)
226 .clear_ambiguous_times(files?, now.extract(py)?)
226 .map_err(|e| v2_error(py, e))?;
227 .map_err(|e| v2_error(py, e))?;
227 Ok(py.None())
228 Ok(py.None())
228 }
229 }
229
230
230 def other_parent_entries(&self) -> PyResult<PyObject> {
231 def other_parent_entries(&self) -> PyResult<PyObject> {
231 let mut inner_shared = self.inner(py).borrow_mut();
232 let mut inner_shared = self.inner(py).borrow_mut();
232 let set = PySet::empty(py)?;
233 let set = PySet::empty(py)?;
233 for path in inner_shared.iter_other_parent_paths() {
234 for path in inner_shared.iter_other_parent_paths() {
234 let path = path.map_err(|e| v2_error(py, e))?;
235 let path = path.map_err(|e| v2_error(py, e))?;
235 set.add(py, PyBytes::new(py, path.as_bytes()))?;
236 set.add(py, PyBytes::new(py, path.as_bytes()))?;
236 }
237 }
237 Ok(set.into_object())
238 Ok(set.into_object())
238 }
239 }
239
240
240 def non_normal_entries(&self) -> PyResult<NonNormalEntries> {
241 def non_normal_entries(&self) -> PyResult<NonNormalEntries> {
241 NonNormalEntries::from_inner(py, self.clone_ref(py))
242 NonNormalEntries::from_inner(py, self.clone_ref(py))
242 }
243 }
243
244
244 def non_normal_entries_contains(&self, key: PyObject) -> PyResult<bool> {
245 def non_normal_entries_contains(&self, key: PyObject) -> PyResult<bool> {
245 let key = key.extract::<PyBytes>(py)?;
246 let key = key.extract::<PyBytes>(py)?;
246 self.inner(py)
247 self.inner(py)
247 .borrow_mut()
248 .borrow_mut()
248 .non_normal_entries_contains(HgPath::new(key.data(py)))
249 .non_normal_entries_contains(HgPath::new(key.data(py)))
249 .map_err(|e| v2_error(py, e))
250 .map_err(|e| v2_error(py, e))
250 }
251 }
251
252
252 def non_normal_entries_display(&self) -> PyResult<PyString> {
253 def non_normal_entries_display(&self) -> PyResult<PyString> {
253 let mut inner = self.inner(py).borrow_mut();
254 let mut inner = self.inner(py).borrow_mut();
254 let paths = inner
255 let paths = inner
255 .iter_non_normal_paths()
256 .iter_non_normal_paths()
256 .collect::<Result<Vec<_>, _>>()
257 .collect::<Result<Vec<_>, _>>()
257 .map_err(|e| v2_error(py, e))?;
258 .map_err(|e| v2_error(py, e))?;
258 let formatted = format!("NonNormalEntries: {}", hg::utils::join_display(paths, ", "));
259 let formatted = format!("NonNormalEntries: {}", hg::utils::join_display(paths, ", "));
259 Ok(PyString::new(py, &formatted))
260 Ok(PyString::new(py, &formatted))
260 }
261 }
261
262
262 def non_normal_entries_remove(&self, key: PyObject) -> PyResult<PyObject> {
263 def non_normal_entries_remove(&self, key: PyObject) -> PyResult<PyObject> {
263 let key = key.extract::<PyBytes>(py)?;
264 let key = key.extract::<PyBytes>(py)?;
264 self
265 self
265 .inner(py)
266 .inner(py)
266 .borrow_mut()
267 .borrow_mut()
267 .non_normal_entries_remove(HgPath::new(key.data(py)));
268 .non_normal_entries_remove(HgPath::new(key.data(py)));
268 Ok(py.None())
269 Ok(py.None())
269 }
270 }
270
271
271 def non_normal_or_other_parent_paths(&self) -> PyResult<PyList> {
272 def non_normal_or_other_parent_paths(&self) -> PyResult<PyList> {
272 let mut inner = self.inner(py).borrow_mut();
273 let mut inner = self.inner(py).borrow_mut();
273
274
274 let ret = PyList::new(py, &[]);
275 let ret = PyList::new(py, &[]);
275 for filename in inner.non_normal_or_other_parent_paths() {
276 for filename in inner.non_normal_or_other_parent_paths() {
276 let filename = filename.map_err(|e| v2_error(py, e))?;
277 let filename = filename.map_err(|e| v2_error(py, e))?;
277 let as_pystring = PyBytes::new(py, filename.as_bytes());
278 let as_pystring = PyBytes::new(py, filename.as_bytes());
278 ret.append(py, as_pystring.into_object());
279 ret.append(py, as_pystring.into_object());
279 }
280 }
280 Ok(ret)
281 Ok(ret)
281 }
282 }
282
283
283 def non_normal_entries_iter(&self) -> PyResult<NonNormalEntriesIterator> {
284 def non_normal_entries_iter(&self) -> PyResult<NonNormalEntriesIterator> {
284 // Make sure the sets are defined before we no longer have a mutable
285 // Make sure the sets are defined before we no longer have a mutable
285 // reference to the dmap.
286 // reference to the dmap.
286 self.inner(py)
287 self.inner(py)
287 .borrow_mut()
288 .borrow_mut()
288 .set_non_normal_other_parent_entries(false);
289 .set_non_normal_other_parent_entries(false);
289
290
290 let leaked_ref = self.inner(py).leak_immutable();
291 let leaked_ref = self.inner(py).leak_immutable();
291
292
292 NonNormalEntriesIterator::from_inner(py, unsafe {
293 NonNormalEntriesIterator::from_inner(py, unsafe {
293 leaked_ref.map(py, |o| {
294 leaked_ref.map(py, |o| {
294 o.iter_non_normal_paths_panic()
295 o.iter_non_normal_paths_panic()
295 })
296 })
296 })
297 })
297 }
298 }
298
299
299 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
300 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
300 let d = d.extract::<PyBytes>(py)?;
301 let d = d.extract::<PyBytes>(py)?;
301 Ok(self.inner(py).borrow_mut()
302 Ok(self.inner(py).borrow_mut()
302 .has_tracked_dir(HgPath::new(d.data(py)))
303 .has_tracked_dir(HgPath::new(d.data(py)))
303 .map_err(|e| {
304 .map_err(|e| {
304 PyErr::new::<exc::ValueError, _>(py, e.to_string())
305 PyErr::new::<exc::ValueError, _>(py, e.to_string())
305 })?
306 })?
306 .to_py_object(py))
307 .to_py_object(py))
307 }
308 }
308
309
309 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
310 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
310 let d = d.extract::<PyBytes>(py)?;
311 let d = d.extract::<PyBytes>(py)?;
311 Ok(self.inner(py).borrow_mut()
312 Ok(self.inner(py).borrow_mut()
312 .has_dir(HgPath::new(d.data(py)))
313 .has_dir(HgPath::new(d.data(py)))
313 .map_err(|e| {
314 .map_err(|e| {
314 PyErr::new::<exc::ValueError, _>(py, e.to_string())
315 PyErr::new::<exc::ValueError, _>(py, e.to_string())
315 })?
316 })?
316 .to_py_object(py))
317 .to_py_object(py))
317 }
318 }
318
319
319 def write_v1(
320 def write_v1(
320 &self,
321 &self,
321 p1: PyObject,
322 p1: PyObject,
322 p2: PyObject,
323 p2: PyObject,
323 now: PyObject
324 now: PyObject
324 ) -> PyResult<PyBytes> {
325 ) -> PyResult<PyBytes> {
325 let now = Timestamp(now.extract(py)?);
326 let now = Timestamp(now.extract(py)?);
326
327
327 let mut inner = self.inner(py).borrow_mut();
328 let mut inner = self.inner(py).borrow_mut();
328 let parents = DirstateParents {
329 let parents = DirstateParents {
329 p1: extract_node_id(py, &p1)?,
330 p1: extract_node_id(py, &p1)?,
330 p2: extract_node_id(py, &p2)?,
331 p2: extract_node_id(py, &p2)?,
331 };
332 };
332 let result = inner.pack_v1(parents, now);
333 let result = inner.pack_v1(parents, now);
333 match result {
334 match result {
334 Ok(packed) => Ok(PyBytes::new(py, &packed)),
335 Ok(packed) => Ok(PyBytes::new(py, &packed)),
335 Err(_) => Err(PyErr::new::<exc::OSError, _>(
336 Err(_) => Err(PyErr::new::<exc::OSError, _>(
336 py,
337 py,
337 "Dirstate error".to_string(),
338 "Dirstate error".to_string(),
338 )),
339 )),
339 }
340 }
340 }
341 }
341
342
342 def write_v2(
343 def write_v2(
343 &self,
344 &self,
344 now: PyObject
345 now: PyObject
345 ) -> PyResult<PyBytes> {
346 ) -> PyResult<PyBytes> {
346 let now = Timestamp(now.extract(py)?);
347 let now = Timestamp(now.extract(py)?);
347
348
348 let mut inner = self.inner(py).borrow_mut();
349 let mut inner = self.inner(py).borrow_mut();
349 let result = inner.pack_v2(now);
350 let result = inner.pack_v2(now);
350 match result {
351 match result {
351 Ok(packed) => Ok(PyBytes::new(py, &packed)),
352 Ok(packed) => Ok(PyBytes::new(py, &packed)),
352 Err(_) => Err(PyErr::new::<exc::OSError, _>(
353 Err(_) => Err(PyErr::new::<exc::OSError, _>(
353 py,
354 py,
354 "Dirstate error".to_string(),
355 "Dirstate error".to_string(),
355 )),
356 )),
356 }
357 }
357 }
358 }
358
359
359 def filefoldmapasdict(&self) -> PyResult<PyDict> {
360 def filefoldmapasdict(&self) -> PyResult<PyDict> {
360 let dict = PyDict::new(py);
361 let dict = PyDict::new(py);
361 for item in self.inner(py).borrow_mut().iter() {
362 for item in self.inner(py).borrow_mut().iter() {
362 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
363 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
363 if entry.state != EntryState::Removed {
364 if entry.state != EntryState::Removed {
364 let key = normalize_case(path);
365 let key = normalize_case(path);
365 let value = path;
366 let value = path;
366 dict.set_item(
367 dict.set_item(
367 py,
368 py,
368 PyBytes::new(py, key.as_bytes()).into_object(),
369 PyBytes::new(py, key.as_bytes()).into_object(),
369 PyBytes::new(py, value.as_bytes()).into_object(),
370 PyBytes::new(py, value.as_bytes()).into_object(),
370 )?;
371 )?;
371 }
372 }
372 }
373 }
373 Ok(dict)
374 Ok(dict)
374 }
375 }
375
376
376 def __len__(&self) -> PyResult<usize> {
377 def __len__(&self) -> PyResult<usize> {
377 Ok(self.inner(py).borrow().len())
378 Ok(self.inner(py).borrow().len())
378 }
379 }
379
380
380 def __contains__(&self, key: PyObject) -> PyResult<bool> {
381 def __contains__(&self, key: PyObject) -> PyResult<bool> {
381 let key = key.extract::<PyBytes>(py)?;
382 let key = key.extract::<PyBytes>(py)?;
382 self.inner(py)
383 self.inner(py)
383 .borrow()
384 .borrow()
384 .contains_key(HgPath::new(key.data(py)))
385 .contains_key(HgPath::new(key.data(py)))
385 .map_err(|e| v2_error(py, e))
386 .map_err(|e| v2_error(py, e))
386 }
387 }
387
388
388 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
389 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
389 let key = key.extract::<PyBytes>(py)?;
390 let key = key.extract::<PyBytes>(py)?;
390 let key = HgPath::new(key.data(py));
391 let key = HgPath::new(key.data(py));
391 match self
392 match self
392 .inner(py)
393 .inner(py)
393 .borrow()
394 .borrow()
394 .get(key)
395 .get(key)
395 .map_err(|e| v2_error(py, e))?
396 .map_err(|e| v2_error(py, e))?
396 {
397 {
397 Some(entry) => {
398 Some(entry) => {
398 Ok(make_dirstate_item(py, &entry)?)
399 Ok(make_dirstate_item(py, &entry)?)
399 },
400 },
400 None => Err(PyErr::new::<exc::KeyError, _>(
401 None => Err(PyErr::new::<exc::KeyError, _>(
401 py,
402 py,
402 String::from_utf8_lossy(key.as_bytes()),
403 String::from_utf8_lossy(key.as_bytes()),
403 )),
404 )),
404 }
405 }
405 }
406 }
406
407
407 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
408 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
408 let leaked_ref = self.inner(py).leak_immutable();
409 let leaked_ref = self.inner(py).leak_immutable();
409 DirstateMapKeysIterator::from_inner(
410 DirstateMapKeysIterator::from_inner(
410 py,
411 py,
411 unsafe { leaked_ref.map(py, |o| o.iter()) },
412 unsafe { leaked_ref.map(py, |o| o.iter()) },
412 )
413 )
413 }
414 }
414
415
415 def items(&self) -> PyResult<DirstateMapItemsIterator> {
416 def items(&self) -> PyResult<DirstateMapItemsIterator> {
416 let leaked_ref = self.inner(py).leak_immutable();
417 let leaked_ref = self.inner(py).leak_immutable();
417 DirstateMapItemsIterator::from_inner(
418 DirstateMapItemsIterator::from_inner(
418 py,
419 py,
419 unsafe { leaked_ref.map(py, |o| o.iter()) },
420 unsafe { leaked_ref.map(py, |o| o.iter()) },
420 )
421 )
421 }
422 }
422
423
423 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
424 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
424 let leaked_ref = self.inner(py).leak_immutable();
425 let leaked_ref = self.inner(py).leak_immutable();
425 DirstateMapKeysIterator::from_inner(
426 DirstateMapKeysIterator::from_inner(
426 py,
427 py,
427 unsafe { leaked_ref.map(py, |o| o.iter()) },
428 unsafe { leaked_ref.map(py, |o| o.iter()) },
428 )
429 )
429 }
430 }
430
431
431 // TODO all copymap* methods, see docstring above
432 // TODO all copymap* methods, see docstring above
432 def copymapcopy(&self) -> PyResult<PyDict> {
433 def copymapcopy(&self) -> PyResult<PyDict> {
433 let dict = PyDict::new(py);
434 let dict = PyDict::new(py);
434 for item in self.inner(py).borrow().copy_map_iter() {
435 for item in self.inner(py).borrow().copy_map_iter() {
435 let (key, value) = item.map_err(|e| v2_error(py, e))?;
436 let (key, value) = item.map_err(|e| v2_error(py, e))?;
436 dict.set_item(
437 dict.set_item(
437 py,
438 py,
438 PyBytes::new(py, key.as_bytes()),
439 PyBytes::new(py, key.as_bytes()),
439 PyBytes::new(py, value.as_bytes()),
440 PyBytes::new(py, value.as_bytes()),
440 )?;
441 )?;
441 }
442 }
442 Ok(dict)
443 Ok(dict)
443 }
444 }
444
445
445 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
446 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
446 let key = key.extract::<PyBytes>(py)?;
447 let key = key.extract::<PyBytes>(py)?;
447 match self
448 match self
448 .inner(py)
449 .inner(py)
449 .borrow()
450 .borrow()
450 .copy_map_get(HgPath::new(key.data(py)))
451 .copy_map_get(HgPath::new(key.data(py)))
451 .map_err(|e| v2_error(py, e))?
452 .map_err(|e| v2_error(py, e))?
452 {
453 {
453 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
454 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
454 None => Err(PyErr::new::<exc::KeyError, _>(
455 None => Err(PyErr::new::<exc::KeyError, _>(
455 py,
456 py,
456 String::from_utf8_lossy(key.data(py)),
457 String::from_utf8_lossy(key.data(py)),
457 )),
458 )),
458 }
459 }
459 }
460 }
460 def copymap(&self) -> PyResult<CopyMap> {
461 def copymap(&self) -> PyResult<CopyMap> {
461 CopyMap::from_inner(py, self.clone_ref(py))
462 CopyMap::from_inner(py, self.clone_ref(py))
462 }
463 }
463
464
464 def copymaplen(&self) -> PyResult<usize> {
465 def copymaplen(&self) -> PyResult<usize> {
465 Ok(self.inner(py).borrow().copy_map_len())
466 Ok(self.inner(py).borrow().copy_map_len())
466 }
467 }
467 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
468 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
468 let key = key.extract::<PyBytes>(py)?;
469 let key = key.extract::<PyBytes>(py)?;
469 self.inner(py)
470 self.inner(py)
470 .borrow()
471 .borrow()
471 .copy_map_contains_key(HgPath::new(key.data(py)))
472 .copy_map_contains_key(HgPath::new(key.data(py)))
472 .map_err(|e| v2_error(py, e))
473 .map_err(|e| v2_error(py, e))
473 }
474 }
474 def copymapget(
475 def copymapget(
475 &self,
476 &self,
476 key: PyObject,
477 key: PyObject,
477 default: Option<PyObject>
478 default: Option<PyObject>
478 ) -> PyResult<Option<PyObject>> {
479 ) -> PyResult<Option<PyObject>> {
479 let key = key.extract::<PyBytes>(py)?;
480 let key = key.extract::<PyBytes>(py)?;
480 match self
481 match self
481 .inner(py)
482 .inner(py)
482 .borrow()
483 .borrow()
483 .copy_map_get(HgPath::new(key.data(py)))
484 .copy_map_get(HgPath::new(key.data(py)))
484 .map_err(|e| v2_error(py, e))?
485 .map_err(|e| v2_error(py, e))?
485 {
486 {
486 Some(copy) => Ok(Some(
487 Some(copy) => Ok(Some(
487 PyBytes::new(py, copy.as_bytes()).into_object(),
488 PyBytes::new(py, copy.as_bytes()).into_object(),
488 )),
489 )),
489 None => Ok(default),
490 None => Ok(default),
490 }
491 }
491 }
492 }
492 def copymapsetitem(
493 def copymapsetitem(
493 &self,
494 &self,
494 key: PyObject,
495 key: PyObject,
495 value: PyObject
496 value: PyObject
496 ) -> PyResult<PyObject> {
497 ) -> PyResult<PyObject> {
497 let key = key.extract::<PyBytes>(py)?;
498 let key = key.extract::<PyBytes>(py)?;
498 let value = value.extract::<PyBytes>(py)?;
499 let value = value.extract::<PyBytes>(py)?;
499 self.inner(py)
500 self.inner(py)
500 .borrow_mut()
501 .borrow_mut()
501 .copy_map_insert(
502 .copy_map_insert(
502 HgPathBuf::from_bytes(key.data(py)),
503 HgPathBuf::from_bytes(key.data(py)),
503 HgPathBuf::from_bytes(value.data(py)),
504 HgPathBuf::from_bytes(value.data(py)),
504 )
505 )
505 .map_err(|e| v2_error(py, e))?;
506 .map_err(|e| v2_error(py, e))?;
506 Ok(py.None())
507 Ok(py.None())
507 }
508 }
508 def copymappop(
509 def copymappop(
509 &self,
510 &self,
510 key: PyObject,
511 key: PyObject,
511 default: Option<PyObject>
512 default: Option<PyObject>
512 ) -> PyResult<Option<PyObject>> {
513 ) -> PyResult<Option<PyObject>> {
513 let key = key.extract::<PyBytes>(py)?;
514 let key = key.extract::<PyBytes>(py)?;
514 match self
515 match self
515 .inner(py)
516 .inner(py)
516 .borrow_mut()
517 .borrow_mut()
517 .copy_map_remove(HgPath::new(key.data(py)))
518 .copy_map_remove(HgPath::new(key.data(py)))
518 .map_err(|e| v2_error(py, e))?
519 .map_err(|e| v2_error(py, e))?
519 {
520 {
520 Some(_) => Ok(None),
521 Some(_) => Ok(None),
521 None => Ok(default),
522 None => Ok(default),
522 }
523 }
523 }
524 }
524
525
525 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
526 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
526 let leaked_ref = self.inner(py).leak_immutable();
527 let leaked_ref = self.inner(py).leak_immutable();
527 CopyMapKeysIterator::from_inner(
528 CopyMapKeysIterator::from_inner(
528 py,
529 py,
529 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
530 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
530 )
531 )
531 }
532 }
532
533
533 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
534 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
534 let leaked_ref = self.inner(py).leak_immutable();
535 let leaked_ref = self.inner(py).leak_immutable();
535 CopyMapItemsIterator::from_inner(
536 CopyMapItemsIterator::from_inner(
536 py,
537 py,
537 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
538 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
538 )
539 )
539 }
540 }
540
541
541 def directories(&self) -> PyResult<PyList> {
542 def directories(&self) -> PyResult<PyList> {
542 let dirs = PyList::new(py, &[]);
543 let dirs = PyList::new(py, &[]);
543 for item in self.inner(py).borrow().iter_directories() {
544 for item in self.inner(py).borrow().iter_directories() {
544 let (path, mtime) = item.map_err(|e| v2_error(py, e))?;
545 let (path, mtime) = item.map_err(|e| v2_error(py, e))?;
545 let path = PyBytes::new(py, path.as_bytes());
546 let path = PyBytes::new(py, path.as_bytes());
546 let mtime = mtime.map(|t| t.0).unwrap_or(-1);
547 let mtime = mtime.map(|t| t.0).unwrap_or(-1);
547 let item = make_directory_item(py, mtime as i32)?;
548 let item = make_directory_item(py, mtime as i32)?;
548 let tuple = (path, item);
549 let tuple = (path, item);
549 dirs.append(py, tuple.to_py_object(py).into_object())
550 dirs.append(py, tuple.to_py_object(py).into_object())
550 }
551 }
551 Ok(dirs)
552 Ok(dirs)
552 }
553 }
553
554
554 });
555 });
555
556
556 impl DirstateMap {
557 impl DirstateMap {
557 pub fn get_inner_mut<'a>(
558 pub fn get_inner_mut<'a>(
558 &'a self,
559 &'a self,
559 py: Python<'a>,
560 py: Python<'a>,
560 ) -> RefMut<'a, Box<dyn DirstateMapMethods + Send>> {
561 ) -> RefMut<'a, Box<dyn DirstateMapMethods + Send>> {
561 self.inner(py).borrow_mut()
562 self.inner(py).borrow_mut()
562 }
563 }
563 fn translate_key(
564 fn translate_key(
564 py: Python,
565 py: Python,
565 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
566 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
566 ) -> PyResult<Option<PyBytes>> {
567 ) -> PyResult<Option<PyBytes>> {
567 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
568 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
568 Ok(Some(PyBytes::new(py, f.as_bytes())))
569 Ok(Some(PyBytes::new(py, f.as_bytes())))
569 }
570 }
570 fn translate_key_value(
571 fn translate_key_value(
571 py: Python,
572 py: Python,
572 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
573 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
573 ) -> PyResult<Option<(PyBytes, PyObject)>> {
574 ) -> PyResult<Option<(PyBytes, PyObject)>> {
574 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
575 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
575 Ok(Some((
576 Ok(Some((
576 PyBytes::new(py, f.as_bytes()),
577 PyBytes::new(py, f.as_bytes()),
577 make_dirstate_item(py, &entry)?,
578 make_dirstate_item(py, &entry)?,
578 )))
579 )))
579 }
580 }
580 }
581 }
581
582
582 py_shared_iterator!(
583 py_shared_iterator!(
583 DirstateMapKeysIterator,
584 DirstateMapKeysIterator,
584 UnsafePyLeaked<StateMapIter<'static>>,
585 UnsafePyLeaked<StateMapIter<'static>>,
585 DirstateMap::translate_key,
586 DirstateMap::translate_key,
586 Option<PyBytes>
587 Option<PyBytes>
587 );
588 );
588
589
589 py_shared_iterator!(
590 py_shared_iterator!(
590 DirstateMapItemsIterator,
591 DirstateMapItemsIterator,
591 UnsafePyLeaked<StateMapIter<'static>>,
592 UnsafePyLeaked<StateMapIter<'static>>,
592 DirstateMap::translate_key_value,
593 DirstateMap::translate_key_value,
593 Option<(PyBytes, PyObject)>
594 Option<(PyBytes, PyObject)>
594 );
595 );
595
596
596 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
597 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
597 let bytes = obj.extract::<PyBytes>(py)?;
598 let bytes = obj.extract::<PyBytes>(py)?;
598 match bytes.data(py).try_into() {
599 match bytes.data(py).try_into() {
599 Ok(s) => Ok(s),
600 Ok(s) => Ok(s),
600 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
601 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
601 }
602 }
602 }
603 }
603
604
604 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
605 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
605 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
606 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
606 }
607 }
@@ -1,114 +1,115 b''
1 use cpython::PyBytes;
1 use cpython::PyBytes;
2 use cpython::Python;
2 use cpython::Python;
3 use hg::dirstate_tree::dirstate_map::DirstateMap;
3 use hg::dirstate_tree::dirstate_map::DirstateMap;
4 use hg::DirstateError;
4 use hg::DirstateError;
5 use hg::DirstateParents;
5 use hg::DirstateParents;
6
6
7 /// Keep a `DirstateMap<'on_disk>` next to the `on_disk` buffer that it
7 /// Keep a `DirstateMap<'on_disk>` next to the `on_disk` buffer that it
8 /// borrows. This is similar to the owning-ref crate.
8 /// borrows. This is similar to the owning-ref crate.
9 ///
9 ///
10 /// This is similar to [`OwningRef`] which is more limited because it
10 /// This is similar to [`OwningRef`] which is more limited because it
11 /// represents exactly one `&T` reference next to the value it borrows, as
11 /// represents exactly one `&T` reference next to the value it borrows, as
12 /// opposed to a struct that may contain an arbitrary number of references in
12 /// opposed to a struct that may contain an arbitrary number of references in
13 /// arbitrarily-nested data structures.
13 /// arbitrarily-nested data structures.
14 ///
14 ///
15 /// [`OwningRef`]: https://docs.rs/owning_ref/0.4.1/owning_ref/struct.OwningRef.html
15 /// [`OwningRef`]: https://docs.rs/owning_ref/0.4.1/owning_ref/struct.OwningRef.html
16 pub(super) struct OwningDirstateMap {
16 pub(super) struct OwningDirstateMap {
17 /// Owned handle to a bytes buffer with a stable address.
17 /// Owned handle to a bytes buffer with a stable address.
18 ///
18 ///
19 /// See <https://docs.rs/owning_ref/0.4.1/owning_ref/trait.StableAddress.html>.
19 /// See <https://docs.rs/owning_ref/0.4.1/owning_ref/trait.StableAddress.html>.
20 on_disk: PyBytes,
20 on_disk: PyBytes,
21
21
22 /// Pointer for `Box<DirstateMap<'on_disk>>`, typed-erased because the
22 /// Pointer for `Box<DirstateMap<'on_disk>>`, typed-erased because the
23 /// language cannot represent a lifetime referencing a sibling field.
23 /// language cannot represent a lifetime referencing a sibling field.
24 /// This is not quite a self-referencial struct (moving this struct is not
24 /// This is not quite a self-referencial struct (moving this struct is not
25 /// a problem as it doesn’t change the address of the bytes buffer owned
25 /// a problem as it doesn’t change the address of the bytes buffer owned
26 /// by `PyBytes`) but touches similar borrow-checker limitations.
26 /// by `PyBytes`) but touches similar borrow-checker limitations.
27 ptr: *mut (),
27 ptr: *mut (),
28 }
28 }
29
29
30 impl OwningDirstateMap {
30 impl OwningDirstateMap {
31 pub fn new_v1(
31 pub fn new_v1(
32 py: Python,
32 py: Python,
33 on_disk: PyBytes,
33 on_disk: PyBytes,
34 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
34 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
35 let bytes: &'_ [u8] = on_disk.data(py);
35 let bytes: &'_ [u8] = on_disk.data(py);
36 let (map, parents) = DirstateMap::new_v1(bytes)?;
36 let (map, parents) = DirstateMap::new_v1(bytes)?;
37
37
38 // Like in `bytes` above, this `'_` lifetime parameter borrows from
38 // Like in `bytes` above, this `'_` lifetime parameter borrows from
39 // the bytes buffer owned by `on_disk`.
39 // the bytes buffer owned by `on_disk`.
40 let ptr: *mut DirstateMap<'_> = Box::into_raw(Box::new(map));
40 let ptr: *mut DirstateMap<'_> = Box::into_raw(Box::new(map));
41
41
42 // Erase the pointed type entirely in order to erase the lifetime.
42 // Erase the pointed type entirely in order to erase the lifetime.
43 let ptr: *mut () = ptr.cast();
43 let ptr: *mut () = ptr.cast();
44
44
45 Ok((Self { on_disk, ptr }, parents))
45 Ok((Self { on_disk, ptr }, parents))
46 }
46 }
47
47
48 pub fn new_v2(
48 pub fn new_v2(
49 py: Python,
49 py: Python,
50 on_disk: PyBytes,
50 on_disk: PyBytes,
51 data_size: usize,
51 ) -> Result<Self, DirstateError> {
52 ) -> Result<Self, DirstateError> {
52 let bytes: &'_ [u8] = on_disk.data(py);
53 let bytes: &'_ [u8] = on_disk.data(py);
53 let map = DirstateMap::new_v2(bytes)?;
54 let map = DirstateMap::new_v2(bytes, data_size)?;
54
55
55 // Like in `bytes` above, this `'_` lifetime parameter borrows from
56 // Like in `bytes` above, this `'_` lifetime parameter borrows from
56 // the bytes buffer owned by `on_disk`.
57 // the bytes buffer owned by `on_disk`.
57 let ptr: *mut DirstateMap<'_> = Box::into_raw(Box::new(map));
58 let ptr: *mut DirstateMap<'_> = Box::into_raw(Box::new(map));
58
59
59 // Erase the pointed type entirely in order to erase the lifetime.
60 // Erase the pointed type entirely in order to erase the lifetime.
60 let ptr: *mut () = ptr.cast();
61 let ptr: *mut () = ptr.cast();
61
62
62 Ok(Self { on_disk, ptr })
63 Ok(Self { on_disk, ptr })
63 }
64 }
64
65
65 pub fn get_mut<'a>(&'a mut self) -> &'a mut DirstateMap<'a> {
66 pub fn get_mut<'a>(&'a mut self) -> &'a mut DirstateMap<'a> {
66 // SAFETY: We cast the type-erased pointer back to the same type it had
67 // SAFETY: We cast the type-erased pointer back to the same type it had
67 // in `new`, except with a different lifetime parameter. This time we
68 // in `new`, except with a different lifetime parameter. This time we
68 // connect the lifetime to that of `self`. This cast is valid because
69 // connect the lifetime to that of `self`. This cast is valid because
69 // `self` owns the same `PyBytes` whose buffer `DirstateMap`
70 // `self` owns the same `PyBytes` whose buffer `DirstateMap`
70 // references. That buffer has a stable memory address because the byte
71 // references. That buffer has a stable memory address because the byte
71 // string value of a `PyBytes` is immutable.
72 // string value of a `PyBytes` is immutable.
72 let ptr: *mut DirstateMap<'a> = self.ptr.cast();
73 let ptr: *mut DirstateMap<'a> = self.ptr.cast();
73 // SAFETY: we dereference that pointer, connecting the lifetime of the
74 // SAFETY: we dereference that pointer, connecting the lifetime of the
74 // new `&mut` to that of `self`. This is valid because the
75 // new `&mut` to that of `self`. This is valid because the
75 // raw pointer is to a boxed value, and `self` owns that box.
76 // raw pointer is to a boxed value, and `self` owns that box.
76 unsafe { &mut *ptr }
77 unsafe { &mut *ptr }
77 }
78 }
78
79
79 pub fn get<'a>(&'a self) -> &'a DirstateMap<'a> {
80 pub fn get<'a>(&'a self) -> &'a DirstateMap<'a> {
80 // SAFETY: same reasoning as in `get_mut` above.
81 // SAFETY: same reasoning as in `get_mut` above.
81 let ptr: *mut DirstateMap<'a> = self.ptr.cast();
82 let ptr: *mut DirstateMap<'a> = self.ptr.cast();
82 unsafe { &*ptr }
83 unsafe { &*ptr }
83 }
84 }
84 }
85 }
85
86
86 impl Drop for OwningDirstateMap {
87 impl Drop for OwningDirstateMap {
87 fn drop(&mut self) {
88 fn drop(&mut self) {
88 // Silence a "field is never read" warning, and demonstrate that this
89 // Silence a "field is never read" warning, and demonstrate that this
89 // value is still alive.
90 // value is still alive.
90 let _ = &self.on_disk;
91 let _ = &self.on_disk;
91 // SAFETY: this cast is the same as in `get_mut`, and is valid for the
92 // SAFETY: this cast is the same as in `get_mut`, and is valid for the
92 // same reason. `self.on_disk` still exists at this point, drop glue
93 // same reason. `self.on_disk` still exists at this point, drop glue
93 // will drop it implicitly after this `drop` method returns.
94 // will drop it implicitly after this `drop` method returns.
94 let ptr: *mut DirstateMap<'_> = self.ptr.cast();
95 let ptr: *mut DirstateMap<'_> = self.ptr.cast();
95 // SAFETY: `Box::from_raw` takes ownership of the box away from `self`.
96 // SAFETY: `Box::from_raw` takes ownership of the box away from `self`.
96 // This is fine because drop glue does nothig for `*mut ()` and we’re
97 // This is fine because drop glue does nothig for `*mut ()` and we’re
97 // in `drop`, so `get` and `get_mut` cannot be called again.
98 // in `drop`, so `get` and `get_mut` cannot be called again.
98 unsafe { drop(Box::from_raw(ptr)) }
99 unsafe { drop(Box::from_raw(ptr)) }
99 }
100 }
100 }
101 }
101
102
102 fn _static_assert_is_send<T: Send>() {}
103 fn _static_assert_is_send<T: Send>() {}
103
104
104 fn _static_assert_fields_are_send() {
105 fn _static_assert_fields_are_send() {
105 _static_assert_is_send::<PyBytes>();
106 _static_assert_is_send::<PyBytes>();
106 _static_assert_is_send::<Box<DirstateMap<'_>>>();
107 _static_assert_is_send::<Box<DirstateMap<'_>>>();
107 }
108 }
108
109
109 // SAFETY: we don’t get this impl implicitly because `*mut (): !Send` because
110 // SAFETY: we don’t get this impl implicitly because `*mut (): !Send` because
110 // thread-safety of raw pointers is unknown in the general case. However this
111 // thread-safety of raw pointers is unknown in the general case. However this
111 // particular raw pointer represents a `Box<DirstateMap<'on_disk>>` that we
112 // particular raw pointer represents a `Box<DirstateMap<'on_disk>>` that we
112 // own. Since that `Box` and `PyBytes` are both `Send` as shown in above, it
113 // own. Since that `Box` and `PyBytes` are both `Send` as shown in above, it
113 // is sound to mark this struct as `Send` too.
114 // is sound to mark this struct as `Send` too.
114 unsafe impl Send for OwningDirstateMap {}
115 unsafe impl Send for OwningDirstateMap {}
@@ -1,339 +1,342 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
3 // Copyright 2020, Georges Racinet <georges.racinets@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::error::CommandError;
8 use crate::error::CommandError;
9 use crate::ui::Ui;
9 use crate::ui::Ui;
10 use clap::{Arg, SubCommand};
10 use clap::{Arg, SubCommand};
11 use hg;
11 use hg;
12 use hg::dirstate_tree::dirstate_map::DirstateMap;
12 use hg::dirstate_tree::dirstate_map::DirstateMap;
13 use hg::dirstate_tree::on_disk;
13 use hg::dirstate_tree::on_disk;
14 use hg::errors::HgResultExt;
14 use hg::errors::HgResultExt;
15 use hg::errors::IoResultExt;
15 use hg::errors::IoResultExt;
16 use hg::matchers::AlwaysMatcher;
16 use hg::matchers::AlwaysMatcher;
17 use hg::operations::cat;
17 use hg::operations::cat;
18 use hg::repo::Repo;
18 use hg::repo::Repo;
19 use hg::revlog::node::Node;
19 use hg::revlog::node::Node;
20 use hg::utils::hg_path::{hg_path_to_os_string, HgPath};
20 use hg::utils::hg_path::{hg_path_to_os_string, HgPath};
21 use hg::StatusError;
21 use hg::StatusError;
22 use hg::{HgPathCow, StatusOptions};
22 use hg::{HgPathCow, StatusOptions};
23 use log::{info, warn};
23 use log::{info, warn};
24 use std::convert::TryInto;
24 use std::convert::TryInto;
25 use std::fs;
25 use std::fs;
26 use std::io::BufReader;
26 use std::io::BufReader;
27 use std::io::Read;
27 use std::io::Read;
28
28
29 pub const HELP_TEXT: &str = "
29 pub const HELP_TEXT: &str = "
30 Show changed files in the working directory
30 Show changed files in the working directory
31
31
32 This is a pure Rust version of `hg status`.
32 This is a pure Rust version of `hg status`.
33
33
34 Some options might be missing, check the list below.
34 Some options might be missing, check the list below.
35 ";
35 ";
36
36
37 pub fn args() -> clap::App<'static, 'static> {
37 pub fn args() -> clap::App<'static, 'static> {
38 SubCommand::with_name("status")
38 SubCommand::with_name("status")
39 .alias("st")
39 .alias("st")
40 .about(HELP_TEXT)
40 .about(HELP_TEXT)
41 .arg(
41 .arg(
42 Arg::with_name("all")
42 Arg::with_name("all")
43 .help("show status of all files")
43 .help("show status of all files")
44 .short("-A")
44 .short("-A")
45 .long("--all"),
45 .long("--all"),
46 )
46 )
47 .arg(
47 .arg(
48 Arg::with_name("modified")
48 Arg::with_name("modified")
49 .help("show only modified files")
49 .help("show only modified files")
50 .short("-m")
50 .short("-m")
51 .long("--modified"),
51 .long("--modified"),
52 )
52 )
53 .arg(
53 .arg(
54 Arg::with_name("added")
54 Arg::with_name("added")
55 .help("show only added files")
55 .help("show only added files")
56 .short("-a")
56 .short("-a")
57 .long("--added"),
57 .long("--added"),
58 )
58 )
59 .arg(
59 .arg(
60 Arg::with_name("removed")
60 Arg::with_name("removed")
61 .help("show only removed files")
61 .help("show only removed files")
62 .short("-r")
62 .short("-r")
63 .long("--removed"),
63 .long("--removed"),
64 )
64 )
65 .arg(
65 .arg(
66 Arg::with_name("clean")
66 Arg::with_name("clean")
67 .help("show only clean files")
67 .help("show only clean files")
68 .short("-c")
68 .short("-c")
69 .long("--clean"),
69 .long("--clean"),
70 )
70 )
71 .arg(
71 .arg(
72 Arg::with_name("deleted")
72 Arg::with_name("deleted")
73 .help("show only deleted files")
73 .help("show only deleted files")
74 .short("-d")
74 .short("-d")
75 .long("--deleted"),
75 .long("--deleted"),
76 )
76 )
77 .arg(
77 .arg(
78 Arg::with_name("unknown")
78 Arg::with_name("unknown")
79 .help("show only unknown (not tracked) files")
79 .help("show only unknown (not tracked) files")
80 .short("-u")
80 .short("-u")
81 .long("--unknown"),
81 .long("--unknown"),
82 )
82 )
83 .arg(
83 .arg(
84 Arg::with_name("ignored")
84 Arg::with_name("ignored")
85 .help("show only ignored files")
85 .help("show only ignored files")
86 .short("-i")
86 .short("-i")
87 .long("--ignored"),
87 .long("--ignored"),
88 )
88 )
89 }
89 }
90
90
91 /// Pure data type allowing the caller to specify file states to display
91 /// Pure data type allowing the caller to specify file states to display
92 #[derive(Copy, Clone, Debug)]
92 #[derive(Copy, Clone, Debug)]
93 pub struct DisplayStates {
93 pub struct DisplayStates {
94 pub modified: bool,
94 pub modified: bool,
95 pub added: bool,
95 pub added: bool,
96 pub removed: bool,
96 pub removed: bool,
97 pub clean: bool,
97 pub clean: bool,
98 pub deleted: bool,
98 pub deleted: bool,
99 pub unknown: bool,
99 pub unknown: bool,
100 pub ignored: bool,
100 pub ignored: bool,
101 }
101 }
102
102
103 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
103 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
104 modified: true,
104 modified: true,
105 added: true,
105 added: true,
106 removed: true,
106 removed: true,
107 clean: false,
107 clean: false,
108 deleted: true,
108 deleted: true,
109 unknown: true,
109 unknown: true,
110 ignored: false,
110 ignored: false,
111 };
111 };
112
112
113 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
113 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
114 modified: true,
114 modified: true,
115 added: true,
115 added: true,
116 removed: true,
116 removed: true,
117 clean: true,
117 clean: true,
118 deleted: true,
118 deleted: true,
119 unknown: true,
119 unknown: true,
120 ignored: true,
120 ignored: true,
121 };
121 };
122
122
123 impl DisplayStates {
123 impl DisplayStates {
124 pub fn is_empty(&self) -> bool {
124 pub fn is_empty(&self) -> bool {
125 !(self.modified
125 !(self.modified
126 || self.added
126 || self.added
127 || self.removed
127 || self.removed
128 || self.clean
128 || self.clean
129 || self.deleted
129 || self.deleted
130 || self.unknown
130 || self.unknown
131 || self.ignored)
131 || self.ignored)
132 }
132 }
133 }
133 }
134
134
135 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
135 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
136 let status_enabled_default = false;
136 let status_enabled_default = false;
137 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
137 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
138 if !status_enabled.unwrap_or(status_enabled_default) {
138 if !status_enabled.unwrap_or(status_enabled_default) {
139 return Err(CommandError::unsupported(
139 return Err(CommandError::unsupported(
140 "status is experimental in rhg (enable it with 'rhg.status = true' \
140 "status is experimental in rhg (enable it with 'rhg.status = true' \
141 or enable fallback with 'rhg.on-unsupported = fallback')"
141 or enable fallback with 'rhg.on-unsupported = fallback')"
142 ));
142 ));
143 }
143 }
144
144
145 let ui = invocation.ui;
145 let ui = invocation.ui;
146 let args = invocation.subcommand_args;
146 let args = invocation.subcommand_args;
147 let display_states = if args.is_present("all") {
147 let display_states = if args.is_present("all") {
148 // TODO when implementing `--quiet`: it excludes clean files
148 // TODO when implementing `--quiet`: it excludes clean files
149 // from `--all`
149 // from `--all`
150 ALL_DISPLAY_STATES
150 ALL_DISPLAY_STATES
151 } else {
151 } else {
152 let requested = DisplayStates {
152 let requested = DisplayStates {
153 modified: args.is_present("modified"),
153 modified: args.is_present("modified"),
154 added: args.is_present("added"),
154 added: args.is_present("added"),
155 removed: args.is_present("removed"),
155 removed: args.is_present("removed"),
156 clean: args.is_present("clean"),
156 clean: args.is_present("clean"),
157 deleted: args.is_present("deleted"),
157 deleted: args.is_present("deleted"),
158 unknown: args.is_present("unknown"),
158 unknown: args.is_present("unknown"),
159 ignored: args.is_present("ignored"),
159 ignored: args.is_present("ignored"),
160 };
160 };
161 if requested.is_empty() {
161 if requested.is_empty() {
162 DEFAULT_DISPLAY_STATES
162 DEFAULT_DISPLAY_STATES
163 } else {
163 } else {
164 requested
164 requested
165 }
165 }
166 };
166 };
167
167
168 let repo = invocation.repo?;
168 let repo = invocation.repo?;
169 let dirstate_data_mmap;
169 let dirstate_data_mmap;
170 let (mut dmap, parents) = if repo.has_dirstate_v2() {
170 let (mut dmap, parents) = if repo.has_dirstate_v2() {
171 let parents;
171 let parents;
172 let dirstate_data;
172 let dirstate_data;
173 let data_size;
173 if let Some(docket_data) =
174 if let Some(docket_data) =
174 repo.hg_vfs().read("dirstate").io_not_found_as_none()?
175 repo.hg_vfs().read("dirstate").io_not_found_as_none()?
175 {
176 {
176 let docket = on_disk::read_docket(&docket_data)?;
177 let docket = on_disk::read_docket(&docket_data)?;
177 parents = Some(docket.parents());
178 parents = Some(docket.parents());
179 data_size = docket.data_size();
178 dirstate_data_mmap = repo
180 dirstate_data_mmap = repo
179 .hg_vfs()
181 .hg_vfs()
180 .mmap_open(docket.data_filename())
182 .mmap_open(docket.data_filename())
181 .io_not_found_as_none()?;
183 .io_not_found_as_none()?;
182 dirstate_data = dirstate_data_mmap.as_deref().unwrap_or(b"");
184 dirstate_data = dirstate_data_mmap.as_deref().unwrap_or(b"");
183 } else {
185 } else {
184 parents = None;
186 parents = None;
187 data_size = 0;
185 dirstate_data = b"";
188 dirstate_data = b"";
186 }
189 }
187 let dmap = DirstateMap::new_v2(dirstate_data)?;
190 let dmap = DirstateMap::new_v2(dirstate_data, data_size)?;
188 (dmap, parents)
191 (dmap, parents)
189 } else {
192 } else {
190 dirstate_data_mmap =
193 dirstate_data_mmap =
191 repo.hg_vfs().mmap_open("dirstate").io_not_found_as_none()?;
194 repo.hg_vfs().mmap_open("dirstate").io_not_found_as_none()?;
192 let dirstate_data = dirstate_data_mmap.as_deref().unwrap_or(b"");
195 let dirstate_data = dirstate_data_mmap.as_deref().unwrap_or(b"");
193 DirstateMap::new_v1(dirstate_data)?
196 DirstateMap::new_v1(dirstate_data)?
194 };
197 };
195
198
196 let options = StatusOptions {
199 let options = StatusOptions {
197 // TODO should be provided by the dirstate parsing and
200 // TODO should be provided by the dirstate parsing and
198 // hence be stored on dmap. Using a value that assumes we aren't
201 // hence be stored on dmap. Using a value that assumes we aren't
199 // below the time resolution granularity of the FS and the
202 // below the time resolution granularity of the FS and the
200 // dirstate.
203 // dirstate.
201 last_normal_time: 0,
204 last_normal_time: 0,
202 // we're currently supporting file systems with exec flags only
205 // we're currently supporting file systems with exec flags only
203 // anyway
206 // anyway
204 check_exec: true,
207 check_exec: true,
205 list_clean: display_states.clean,
208 list_clean: display_states.clean,
206 list_unknown: display_states.unknown,
209 list_unknown: display_states.unknown,
207 list_ignored: display_states.ignored,
210 list_ignored: display_states.ignored,
208 collect_traversed_dirs: false,
211 collect_traversed_dirs: false,
209 };
212 };
210 let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded
213 let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded
211 let (mut ds_status, pattern_warnings) = hg::dirstate_tree::status::status(
214 let (mut ds_status, pattern_warnings) = hg::dirstate_tree::status::status(
212 &mut dmap,
215 &mut dmap,
213 &AlwaysMatcher,
216 &AlwaysMatcher,
214 repo.working_directory_path().to_owned(),
217 repo.working_directory_path().to_owned(),
215 vec![ignore_file],
218 vec![ignore_file],
216 options,
219 options,
217 )?;
220 )?;
218 if !pattern_warnings.is_empty() {
221 if !pattern_warnings.is_empty() {
219 warn!("Pattern warnings: {:?}", &pattern_warnings);
222 warn!("Pattern warnings: {:?}", &pattern_warnings);
220 }
223 }
221
224
222 if !ds_status.bad.is_empty() {
225 if !ds_status.bad.is_empty() {
223 warn!("Bad matches {:?}", &(ds_status.bad))
226 warn!("Bad matches {:?}", &(ds_status.bad))
224 }
227 }
225 if !ds_status.unsure.is_empty() {
228 if !ds_status.unsure.is_empty() {
226 info!(
229 info!(
227 "Files to be rechecked by retrieval from filelog: {:?}",
230 "Files to be rechecked by retrieval from filelog: {:?}",
228 &ds_status.unsure
231 &ds_status.unsure
229 );
232 );
230 }
233 }
231 if !ds_status.unsure.is_empty()
234 if !ds_status.unsure.is_empty()
232 && (display_states.modified || display_states.clean)
235 && (display_states.modified || display_states.clean)
233 {
236 {
234 let p1: Node = parents
237 let p1: Node = parents
235 .expect(
238 .expect(
236 "Dirstate with no parents should not list any file to
239 "Dirstate with no parents should not list any file to
237 be rechecked for modifications",
240 be rechecked for modifications",
238 )
241 )
239 .p1
242 .p1
240 .into();
243 .into();
241 let p1_hex = format!("{:x}", p1);
244 let p1_hex = format!("{:x}", p1);
242 for to_check in ds_status.unsure {
245 for to_check in ds_status.unsure {
243 if cat_file_is_modified(repo, &to_check, &p1_hex)? {
246 if cat_file_is_modified(repo, &to_check, &p1_hex)? {
244 if display_states.modified {
247 if display_states.modified {
245 ds_status.modified.push(to_check);
248 ds_status.modified.push(to_check);
246 }
249 }
247 } else {
250 } else {
248 if display_states.clean {
251 if display_states.clean {
249 ds_status.clean.push(to_check);
252 ds_status.clean.push(to_check);
250 }
253 }
251 }
254 }
252 }
255 }
253 }
256 }
254 if display_states.modified {
257 if display_states.modified {
255 display_status_paths(ui, &mut ds_status.modified, b"M")?;
258 display_status_paths(ui, &mut ds_status.modified, b"M")?;
256 }
259 }
257 if display_states.added {
260 if display_states.added {
258 display_status_paths(ui, &mut ds_status.added, b"A")?;
261 display_status_paths(ui, &mut ds_status.added, b"A")?;
259 }
262 }
260 if display_states.removed {
263 if display_states.removed {
261 display_status_paths(ui, &mut ds_status.removed, b"R")?;
264 display_status_paths(ui, &mut ds_status.removed, b"R")?;
262 }
265 }
263 if display_states.deleted {
266 if display_states.deleted {
264 display_status_paths(ui, &mut ds_status.deleted, b"!")?;
267 display_status_paths(ui, &mut ds_status.deleted, b"!")?;
265 }
268 }
266 if display_states.unknown {
269 if display_states.unknown {
267 display_status_paths(ui, &mut ds_status.unknown, b"?")?;
270 display_status_paths(ui, &mut ds_status.unknown, b"?")?;
268 }
271 }
269 if display_states.ignored {
272 if display_states.ignored {
270 display_status_paths(ui, &mut ds_status.ignored, b"I")?;
273 display_status_paths(ui, &mut ds_status.ignored, b"I")?;
271 }
274 }
272 if display_states.clean {
275 if display_states.clean {
273 display_status_paths(ui, &mut ds_status.clean, b"C")?;
276 display_status_paths(ui, &mut ds_status.clean, b"C")?;
274 }
277 }
275 Ok(())
278 Ok(())
276 }
279 }
277
280
278 // Probably more elegant to use a Deref or Borrow trait rather than
281 // Probably more elegant to use a Deref or Borrow trait rather than
279 // harcode HgPathBuf, but probably not really useful at this point
282 // harcode HgPathBuf, but probably not really useful at this point
280 fn display_status_paths(
283 fn display_status_paths(
281 ui: &Ui,
284 ui: &Ui,
282 paths: &mut [HgPathCow],
285 paths: &mut [HgPathCow],
283 status_prefix: &[u8],
286 status_prefix: &[u8],
284 ) -> Result<(), CommandError> {
287 ) -> Result<(), CommandError> {
285 paths.sort_unstable();
288 paths.sort_unstable();
286 for path in paths {
289 for path in paths {
287 // Same TODO as in commands::root
290 // Same TODO as in commands::root
288 let bytes: &[u8] = path.as_bytes();
291 let bytes: &[u8] = path.as_bytes();
289 // TODO optim, probably lots of unneeded copies here, especially
292 // TODO optim, probably lots of unneeded copies here, especially
290 // if out stream is buffered
293 // if out stream is buffered
291 ui.write_stdout(&[status_prefix, b" ", bytes, b"\n"].concat())?;
294 ui.write_stdout(&[status_prefix, b" ", bytes, b"\n"].concat())?;
292 }
295 }
293 Ok(())
296 Ok(())
294 }
297 }
295
298
296 /// Check if a file is modified by comparing actual repo store and file system.
299 /// Check if a file is modified by comparing actual repo store and file system.
297 ///
300 ///
298 /// This meant to be used for those that the dirstate cannot resolve, due
301 /// This meant to be used for those that the dirstate cannot resolve, due
299 /// to time resolution limits.
302 /// to time resolution limits.
300 ///
303 ///
301 /// TODO: detect permission bits and similar metadata modifications
304 /// TODO: detect permission bits and similar metadata modifications
302 fn cat_file_is_modified(
305 fn cat_file_is_modified(
303 repo: &Repo,
306 repo: &Repo,
304 hg_path: &HgPath,
307 hg_path: &HgPath,
305 rev: &str,
308 rev: &str,
306 ) -> Result<bool, CommandError> {
309 ) -> Result<bool, CommandError> {
307 // TODO CatRev expects &[HgPathBuf], something like
310 // TODO CatRev expects &[HgPathBuf], something like
308 // &[impl Deref<HgPath>] would be nicer and should avoid the copy
311 // &[impl Deref<HgPath>] would be nicer and should avoid the copy
309 let path_bufs = [hg_path.into()];
312 let path_bufs = [hg_path.into()];
310 // TODO IIUC CatRev returns a simple Vec<u8> for all files
313 // TODO IIUC CatRev returns a simple Vec<u8> for all files
311 // being able to tell them apart as (path, bytes) would be nicer
314 // being able to tell them apart as (path, bytes) would be nicer
312 // and OPTIM would allow manifest resolution just once.
315 // and OPTIM would allow manifest resolution just once.
313 let output = cat(repo, rev, &path_bufs).map_err(|e| (e, rev))?;
316 let output = cat(repo, rev, &path_bufs).map_err(|e| (e, rev))?;
314
317
315 let fs_path = repo
318 let fs_path = repo
316 .working_directory_vfs()
319 .working_directory_vfs()
317 .join(hg_path_to_os_string(hg_path).expect("HgPath conversion"));
320 .join(hg_path_to_os_string(hg_path).expect("HgPath conversion"));
318 let hg_data_len: u64 = match output.concatenated.len().try_into() {
321 let hg_data_len: u64 = match output.concatenated.len().try_into() {
319 Ok(v) => v,
322 Ok(v) => v,
320 Err(_) => {
323 Err(_) => {
321 // conversion of data length to u64 failed,
324 // conversion of data length to u64 failed,
322 // good luck for any file to have this content
325 // good luck for any file to have this content
323 return Ok(true);
326 return Ok(true);
324 }
327 }
325 };
328 };
326 let fobj = fs::File::open(&fs_path).when_reading_file(&fs_path)?;
329 let fobj = fs::File::open(&fs_path).when_reading_file(&fs_path)?;
327 if fobj.metadata().map_err(|e| StatusError::from(e))?.len() != hg_data_len
330 if fobj.metadata().map_err(|e| StatusError::from(e))?.len() != hg_data_len
328 {
331 {
329 return Ok(true);
332 return Ok(true);
330 }
333 }
331 for (fs_byte, hg_byte) in
334 for (fs_byte, hg_byte) in
332 BufReader::new(fobj).bytes().zip(output.concatenated)
335 BufReader::new(fobj).bytes().zip(output.concatenated)
333 {
336 {
334 if fs_byte.map_err(|e| StatusError::from(e))? != hg_byte {
337 if fs_byte.map_err(|e| StatusError::from(e))? != hg_byte {
335 return Ok(true);
338 return Ok(true);
336 }
339 }
337 }
340 }
338 Ok(false)
341 Ok(false)
339 }
342 }
General Comments 0
You need to be logged in to leave comments. Login now