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