##// END OF EJS Templates
rust-dirstatemap: implement part of the `setparents` logic...
Raphaël Gomès -
r50011:3df46f3a default
parent child Browse files
Show More
@@ -1,709 +1,694 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
7 7 import errno
8 8
9 9 from .i18n import _
10 10
11 11 from . import (
12 12 error,
13 13 pathutil,
14 14 policy,
15 15 txnutil,
16 16 util,
17 17 )
18 18
19 19 from .dirstateutils import (
20 20 docket as docketmod,
21 21 v2,
22 22 )
23 23
24 24 parsers = policy.importmod('parsers')
25 25 rustmod = policy.importrust('dirstate')
26 26
27 27 propertycache = util.propertycache
28 28
29 29 if rustmod is None:
30 30 DirstateItem = parsers.DirstateItem
31 31 else:
32 32 DirstateItem = rustmod.DirstateItem
33 33
34 34 rangemask = 0x7FFFFFFF
35 35
36 36
37 37 class _dirstatemapcommon:
38 38 """
39 39 Methods that are identical for both implementations of the dirstatemap
40 40 class, with and without Rust extensions enabled.
41 41 """
42 42
43 43 # please pytype
44 44
45 45 _map = None
46 46 copymap = None
47 47
48 48 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
49 49 self._use_dirstate_v2 = use_dirstate_v2
50 50 self._nodeconstants = nodeconstants
51 51 self._ui = ui
52 52 self._opener = opener
53 53 self._root = root
54 54 self._filename = b'dirstate'
55 55 self._nodelen = 20 # Also update Rust code when changing this!
56 56 self._parents = None
57 57 self._dirtyparents = False
58 58 self._docket = None
59 59
60 60 # for consistent view between _pl() and _read() invocations
61 61 self._pendingmode = None
62 62
63 63 def preload(self):
64 64 """Loads the underlying data, if it's not already loaded"""
65 65 self._map
66 66
67 67 def get(self, key, default=None):
68 68 return self._map.get(key, default)
69 69
70 70 def __len__(self):
71 71 return len(self._map)
72 72
73 73 def __iter__(self):
74 74 return iter(self._map)
75 75
76 76 def __contains__(self, key):
77 77 return key in self._map
78 78
79 79 def __getitem__(self, item):
80 80 return self._map[item]
81 81
82 82 ### disk interaction
83 83
84 84 def _opendirstatefile(self):
85 85 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
86 86 if self._pendingmode is not None and self._pendingmode != mode:
87 87 fp.close()
88 88 raise error.Abort(
89 89 _(b'working directory state may be changed parallelly')
90 90 )
91 91 self._pendingmode = mode
92 92 return fp
93 93
94 94 def _readdirstatefile(self, size=-1):
95 95 try:
96 96 with self._opendirstatefile() as fp:
97 97 return fp.read(size)
98 98 except IOError as err:
99 99 if err.errno != errno.ENOENT:
100 100 raise
101 101 # File doesn't exist, so the current state is empty
102 102 return b''
103 103
104 104 @property
105 105 def docket(self):
106 106 if not self._docket:
107 107 if not self._use_dirstate_v2:
108 108 raise error.ProgrammingError(
109 109 b'dirstate only has a docket in v2 format'
110 110 )
111 111 self._docket = docketmod.DirstateDocket.parse(
112 112 self._readdirstatefile(), self._nodeconstants
113 113 )
114 114 return self._docket
115 115
116 116 def write_v2_no_append(self, tr, st, meta, packed):
117 117 old_docket = self.docket
118 118 new_docket = docketmod.DirstateDocket.with_new_uuid(
119 119 self.parents(), len(packed), meta
120 120 )
121 121 data_filename = new_docket.data_filename()
122 122 if tr:
123 123 tr.add(data_filename, 0)
124 124 self._opener.write(data_filename, packed)
125 125 # Write the new docket after the new data file has been
126 126 # written. Because `st` was opened with `atomictemp=True`,
127 127 # the actual `.hg/dirstate` file is only affected on close.
128 128 st.write(new_docket.serialize())
129 129 st.close()
130 130 # Remove the old data file after the new docket pointing to
131 131 # the new data file was written.
132 132 if old_docket.uuid:
133 133 data_filename = old_docket.data_filename()
134 134 unlink = lambda _tr=None: self._opener.unlink(data_filename)
135 135 if tr:
136 136 category = b"dirstate-v2-clean-" + old_docket.uuid
137 137 tr.addpostclose(category, unlink)
138 138 else:
139 139 unlink()
140 140 self._docket = new_docket
141 141
142 142 ### reading/setting parents
143 143
144 144 def parents(self):
145 145 if not self._parents:
146 146 if self._use_dirstate_v2:
147 147 self._parents = self.docket.parents
148 148 else:
149 149 read_len = self._nodelen * 2
150 150 st = self._readdirstatefile(read_len)
151 151 l = len(st)
152 152 if l == read_len:
153 153 self._parents = (
154 154 st[: self._nodelen],
155 155 st[self._nodelen : 2 * self._nodelen],
156 156 )
157 157 elif l == 0:
158 158 self._parents = (
159 159 self._nodeconstants.nullid,
160 160 self._nodeconstants.nullid,
161 161 )
162 162 else:
163 163 raise error.Abort(
164 164 _(b'working directory state appears damaged!')
165 165 )
166 166
167 167 return self._parents
168 168
169 169
170 170 class dirstatemap(_dirstatemapcommon):
171 171 """Map encapsulating the dirstate's contents.
172 172
173 173 The dirstate contains the following state:
174 174
175 175 - `identity` is the identity of the dirstate file, which can be used to
176 176 detect when changes have occurred to the dirstate file.
177 177
178 178 - `parents` is a pair containing the parents of the working copy. The
179 179 parents are updated by calling `setparents`.
180 180
181 181 - the state map maps filenames to tuples of (state, mode, size, mtime),
182 182 where state is a single character representing 'normal', 'added',
183 183 'removed', or 'merged'. It is read by treating the dirstate as a
184 184 dict. File state is updated by calling various methods (see each
185 185 documentation for details):
186 186
187 187 - `reset_state`,
188 188 - `set_tracked`
189 189 - `set_untracked`
190 190 - `set_clean`
191 191 - `set_possibly_dirty`
192 192
193 193 - `copymap` maps destination filenames to their source filename.
194 194
195 195 The dirstate also provides the following views onto the state:
196 196
197 197 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
198 198 form that they appear as in the dirstate.
199 199
200 200 - `dirfoldmap` is a dict mapping normalized directory names to the
201 201 denormalized form that they appear as in the dirstate.
202 202 """
203 203
204 204 ### Core data storage and access
205 205
206 206 @propertycache
207 207 def _map(self):
208 208 self._map = {}
209 209 self.read()
210 210 return self._map
211 211
212 212 @propertycache
213 213 def copymap(self):
214 214 self.copymap = {}
215 215 self._map
216 216 return self.copymap
217 217
218 218 def clear(self):
219 219 self._map.clear()
220 220 self.copymap.clear()
221 221 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
222 222 util.clearcachedproperty(self, b"_dirs")
223 223 util.clearcachedproperty(self, b"_alldirs")
224 224 util.clearcachedproperty(self, b"filefoldmap")
225 225 util.clearcachedproperty(self, b"dirfoldmap")
226 226
227 227 def items(self):
228 228 return self._map.items()
229 229
230 230 # forward for python2,3 compat
231 231 iteritems = items
232 232
233 233 def debug_iter(self, all):
234 234 """
235 235 Return an iterator of (filename, state, mode, size, mtime) tuples
236 236
237 237 `all` is unused when Rust is not enabled
238 238 """
239 239 for (filename, item) in self.items():
240 240 yield (filename, item.state, item.mode, item.size, item.mtime)
241 241
242 242 def keys(self):
243 243 return self._map.keys()
244 244
245 245 ### reading/setting parents
246 246
247 247 def setparents(self, p1, p2, fold_p2=False):
248 248 self._parents = (p1, p2)
249 249 self._dirtyparents = True
250 250 copies = {}
251 251 if fold_p2:
252 252 for f, s in self._map.items():
253 253 # Discard "merged" markers when moving away from a merge state
254 254 if s.p2_info:
255 255 source = self.copymap.pop(f, None)
256 256 if source:
257 257 copies[f] = source
258 258 s.drop_merge_data()
259 259 return copies
260 260
261 261 ### disk interaction
262 262
263 263 def read(self):
264 264 # ignore HG_PENDING because identity is used only for writing
265 265 self.identity = util.filestat.frompath(
266 266 self._opener.join(self._filename)
267 267 )
268 268
269 269 if self._use_dirstate_v2:
270 270 if not self.docket.uuid:
271 271 return
272 272 st = self._opener.read(self.docket.data_filename())
273 273 else:
274 274 st = self._readdirstatefile()
275 275
276 276 if not st:
277 277 return
278 278
279 279 # TODO: adjust this estimate for dirstate-v2
280 280 if util.safehasattr(parsers, b'dict_new_presized'):
281 281 # Make an estimate of the number of files in the dirstate based on
282 282 # its size. This trades wasting some memory for avoiding costly
283 283 # resizes. Each entry have a prefix of 17 bytes followed by one or
284 284 # two path names. Studies on various large-scale real-world repositories
285 285 # found 54 bytes a reasonable upper limit for the average path names.
286 286 # Copy entries are ignored for the sake of this estimate.
287 287 self._map = parsers.dict_new_presized(len(st) // 71)
288 288
289 289 # Python's garbage collector triggers a GC each time a certain number
290 290 # of container objects (the number being defined by
291 291 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
292 292 # for each file in the dirstate. The C version then immediately marks
293 293 # them as not to be tracked by the collector. However, this has no
294 294 # effect on when GCs are triggered, only on what objects the GC looks
295 295 # into. This means that O(number of files) GCs are unavoidable.
296 296 # Depending on when in the process's lifetime the dirstate is parsed,
297 297 # this can get very expensive. As a workaround, disable GC while
298 298 # parsing the dirstate.
299 299 #
300 300 # (we cannot decorate the function directly since it is in a C module)
301 301 if self._use_dirstate_v2:
302 302 p = self.docket.parents
303 303 meta = self.docket.tree_metadata
304 304 parse_dirstate = util.nogc(v2.parse_dirstate)
305 305 parse_dirstate(self._map, self.copymap, st, meta)
306 306 else:
307 307 parse_dirstate = util.nogc(parsers.parse_dirstate)
308 308 p = parse_dirstate(self._map, self.copymap, st)
309 309 if not self._dirtyparents:
310 310 self.setparents(*p)
311 311
312 312 # Avoid excess attribute lookups by fast pathing certain checks
313 313 self.__contains__ = self._map.__contains__
314 314 self.__getitem__ = self._map.__getitem__
315 315 self.get = self._map.get
316 316
317 317 def write(self, tr, st):
318 318 if self._use_dirstate_v2:
319 319 packed, meta = v2.pack_dirstate(self._map, self.copymap)
320 320 self.write_v2_no_append(tr, st, meta, packed)
321 321 else:
322 322 packed = parsers.pack_dirstate(
323 323 self._map, self.copymap, self.parents()
324 324 )
325 325 st.write(packed)
326 326 st.close()
327 327 self._dirtyparents = False
328 328
329 329 @propertycache
330 330 def identity(self):
331 331 self._map
332 332 return self.identity
333 333
334 334 ### code related to maintaining and accessing "extra" property
335 335 # (e.g. "has_dir")
336 336
337 337 def _dirs_incr(self, filename, old_entry=None):
338 338 """increment the dirstate counter if applicable"""
339 339 if (
340 340 old_entry is None or old_entry.removed
341 341 ) and "_dirs" in self.__dict__:
342 342 self._dirs.addpath(filename)
343 343 if old_entry is None and "_alldirs" in self.__dict__:
344 344 self._alldirs.addpath(filename)
345 345
346 346 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
347 347 """decrement the dirstate counter if applicable"""
348 348 if old_entry is not None:
349 349 if "_dirs" in self.__dict__ and not old_entry.removed:
350 350 self._dirs.delpath(filename)
351 351 if "_alldirs" in self.__dict__ and not remove_variant:
352 352 self._alldirs.delpath(filename)
353 353 elif remove_variant and "_alldirs" in self.__dict__:
354 354 self._alldirs.addpath(filename)
355 355 if "filefoldmap" in self.__dict__:
356 356 normed = util.normcase(filename)
357 357 self.filefoldmap.pop(normed, None)
358 358
359 359 @propertycache
360 360 def filefoldmap(self):
361 361 """Returns a dictionary mapping normalized case paths to their
362 362 non-normalized versions.
363 363 """
364 364 try:
365 365 makefilefoldmap = parsers.make_file_foldmap
366 366 except AttributeError:
367 367 pass
368 368 else:
369 369 return makefilefoldmap(
370 370 self._map, util.normcasespec, util.normcasefallback
371 371 )
372 372
373 373 f = {}
374 374 normcase = util.normcase
375 375 for name, s in self._map.items():
376 376 if not s.removed:
377 377 f[normcase(name)] = name
378 378 f[b'.'] = b'.' # prevents useless util.fspath() invocation
379 379 return f
380 380
381 381 @propertycache
382 382 def dirfoldmap(self):
383 383 f = {}
384 384 normcase = util.normcase
385 385 for name in self._dirs:
386 386 f[normcase(name)] = name
387 387 return f
388 388
389 389 def hastrackeddir(self, d):
390 390 """
391 391 Returns True if the dirstate contains a tracked (not removed) file
392 392 in this directory.
393 393 """
394 394 return d in self._dirs
395 395
396 396 def hasdir(self, d):
397 397 """
398 398 Returns True if the dirstate contains a file (tracked or removed)
399 399 in this directory.
400 400 """
401 401 return d in self._alldirs
402 402
403 403 @propertycache
404 404 def _dirs(self):
405 405 return pathutil.dirs(self._map, only_tracked=True)
406 406
407 407 @propertycache
408 408 def _alldirs(self):
409 409 return pathutil.dirs(self._map)
410 410
411 411 ### code related to manipulation of entries and copy-sources
412 412
413 413 def reset_state(
414 414 self,
415 415 filename,
416 416 wc_tracked=False,
417 417 p1_tracked=False,
418 418 p2_info=False,
419 419 has_meaningful_mtime=True,
420 420 parentfiledata=None,
421 421 ):
422 422 """Set a entry to a given state, diregarding all previous state
423 423
424 424 This is to be used by the part of the dirstate API dedicated to
425 425 adjusting the dirstate after a update/merge.
426 426
427 427 note: calling this might result to no entry existing at all if the
428 428 dirstate map does not see any point at having one for this file
429 429 anymore.
430 430 """
431 431 # copy information are now outdated
432 432 # (maybe new information should be in directly passed to this function)
433 433 self.copymap.pop(filename, None)
434 434
435 435 if not (p1_tracked or p2_info or wc_tracked):
436 436 old_entry = self._map.get(filename)
437 437 self._drop_entry(filename)
438 438 self._dirs_decr(filename, old_entry=old_entry)
439 439 return
440 440
441 441 old_entry = self._map.get(filename)
442 442 self._dirs_incr(filename, old_entry)
443 443 entry = DirstateItem(
444 444 wc_tracked=wc_tracked,
445 445 p1_tracked=p1_tracked,
446 446 p2_info=p2_info,
447 447 has_meaningful_mtime=has_meaningful_mtime,
448 448 parentfiledata=parentfiledata,
449 449 )
450 450 self._map[filename] = entry
451 451
452 452 def set_tracked(self, filename):
453 453 new = False
454 454 entry = self.get(filename)
455 455 if entry is None:
456 456 self._dirs_incr(filename)
457 457 entry = DirstateItem(
458 458 wc_tracked=True,
459 459 )
460 460
461 461 self._map[filename] = entry
462 462 new = True
463 463 elif not entry.tracked:
464 464 self._dirs_incr(filename, entry)
465 465 entry.set_tracked()
466 466 self._refresh_entry(filename, entry)
467 467 new = True
468 468 else:
469 469 # XXX This is probably overkill for more case, but we need this to
470 470 # fully replace the `normallookup` call with `set_tracked` one.
471 471 # Consider smoothing this in the future.
472 472 entry.set_possibly_dirty()
473 473 self._refresh_entry(filename, entry)
474 474 return new
475 475
476 476 def set_untracked(self, f):
477 477 """Mark a file as no longer tracked in the dirstate map"""
478 478 entry = self.get(f)
479 479 if entry is None:
480 480 return False
481 481 else:
482 482 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
483 483 if not entry.p2_info:
484 484 self.copymap.pop(f, None)
485 485 entry.set_untracked()
486 486 self._refresh_entry(f, entry)
487 487 return True
488 488
489 489 def set_clean(self, filename, mode, size, mtime):
490 490 """mark a file as back to a clean state"""
491 491 entry = self[filename]
492 492 size = size & rangemask
493 493 entry.set_clean(mode, size, mtime)
494 494 self._refresh_entry(filename, entry)
495 495 self.copymap.pop(filename, None)
496 496
497 497 def set_possibly_dirty(self, filename):
498 498 """record that the current state of the file on disk is unknown"""
499 499 entry = self[filename]
500 500 entry.set_possibly_dirty()
501 501 self._refresh_entry(filename, entry)
502 502
503 503 def _refresh_entry(self, f, entry):
504 504 """record updated state of an entry"""
505 505 if not entry.any_tracked:
506 506 self._map.pop(f, None)
507 507
508 508 def _drop_entry(self, f):
509 509 """remove any entry for file f
510 510
511 511 This should also drop associated copy information
512 512
513 513 The fact we actually need to drop it is the responsability of the caller"""
514 514 self._map.pop(f, None)
515 515 self.copymap.pop(f, None)
516 516
517 517
518 518 if rustmod is not None:
519 519
520 520 class dirstatemap(_dirstatemapcommon):
521 521
522 522 ### Core data storage and access
523 523
524 524 @propertycache
525 525 def _map(self):
526 526 """
527 527 Fills the Dirstatemap when called.
528 528 """
529 529 # ignore HG_PENDING because identity is used only for writing
530 530 self.identity = util.filestat.frompath(
531 531 self._opener.join(self._filename)
532 532 )
533 533
534 534 if self._use_dirstate_v2:
535 535 if self.docket.uuid:
536 536 # TODO: use mmap when possible
537 537 data = self._opener.read(self.docket.data_filename())
538 538 else:
539 539 data = b''
540 540 self._map = rustmod.DirstateMap.new_v2(
541 541 data, self.docket.data_size, self.docket.tree_metadata
542 542 )
543 543 parents = self.docket.parents
544 544 else:
545 545 self._map, parents = rustmod.DirstateMap.new_v1(
546 546 self._readdirstatefile()
547 547 )
548 548
549 549 if parents and not self._dirtyparents:
550 550 self.setparents(*parents)
551 551
552 552 self.__contains__ = self._map.__contains__
553 553 self.__getitem__ = self._map.__getitem__
554 554 self.get = self._map.get
555 555 return self._map
556 556
557 557 @property
558 558 def copymap(self):
559 559 return self._map.copymap()
560 560
561 561 def debug_iter(self, all):
562 562 """
563 563 Return an iterator of (filename, state, mode, size, mtime) tuples
564 564
565 565 `all`: also include with `state == b' '` dirstate tree nodes that
566 566 don't have an associated `DirstateItem`.
567 567
568 568 """
569 569 return self._map.debug_iter(all)
570 570
571 571 def clear(self):
572 572 self._map.clear()
573 573 self.setparents(
574 574 self._nodeconstants.nullid, self._nodeconstants.nullid
575 575 )
576 576 util.clearcachedproperty(self, b"_dirs")
577 577 util.clearcachedproperty(self, b"_alldirs")
578 578 util.clearcachedproperty(self, b"dirfoldmap")
579 579
580 580 def items(self):
581 581 return self._map.items()
582 582
583 583 # forward for python2,3 compat
584 584 iteritems = items
585 585
586 586 def keys(self):
587 587 return iter(self._map)
588 588
589 589 ### reading/setting parents
590 590
591 591 def setparents(self, p1, p2, fold_p2=False):
592 592 self._parents = (p1, p2)
593 593 self._dirtyparents = True
594 594 copies = {}
595 595 if fold_p2:
596 # Collect into an intermediate list to avoid a `RuntimeError`
597 # exception due to mutation during iteration.
598 # TODO: move this the whole loop to Rust where `iter_mut`
599 # enables in-place mutation of elements of a collection while
600 # iterating it, without mutating the collection itself.
601 files_with_p2_info = [
602 f for f, s in self._map.items() if s.p2_info
603 ]
604 rust_map = self._map
605 for f in files_with_p2_info:
606 e = rust_map.get(f)
607 source = self.copymap.pop(f, None)
608 if source:
609 copies[f] = source
610 e.drop_merge_data()
611 rust_map.set_dirstate_item(f, e)
596 copies = self._map.setparents_fixup()
612 597 return copies
613 598
614 599 ### disk interaction
615 600
616 601 @propertycache
617 602 def identity(self):
618 603 self._map
619 604 return self.identity
620 605
621 606 def write(self, tr, st):
622 607 if not self._use_dirstate_v2:
623 608 p1, p2 = self.parents()
624 609 packed = self._map.write_v1(p1, p2)
625 610 st.write(packed)
626 611 st.close()
627 612 self._dirtyparents = False
628 613 return
629 614
630 615 # We can only append to an existing data file if there is one
631 616 can_append = self.docket.uuid is not None
632 617 packed, meta, append = self._map.write_v2(can_append)
633 618 if append:
634 619 docket = self.docket
635 620 data_filename = docket.data_filename()
636 621 if tr:
637 622 tr.add(data_filename, docket.data_size)
638 623 with self._opener(data_filename, b'r+b') as fp:
639 624 fp.seek(docket.data_size)
640 625 assert fp.tell() == docket.data_size
641 626 written = fp.write(packed)
642 627 if written is not None: # py2 may return None
643 628 assert written == len(packed), (written, len(packed))
644 629 docket.data_size += len(packed)
645 630 docket.parents = self.parents()
646 631 docket.tree_metadata = meta
647 632 st.write(docket.serialize())
648 633 st.close()
649 634 else:
650 635 self.write_v2_no_append(tr, st, meta, packed)
651 636 # Reload from the newly-written file
652 637 util.clearcachedproperty(self, b"_map")
653 638 self._dirtyparents = False
654 639
655 640 ### code related to maintaining and accessing "extra" property
656 641 # (e.g. "has_dir")
657 642
658 643 @propertycache
659 644 def filefoldmap(self):
660 645 """Returns a dictionary mapping normalized case paths to their
661 646 non-normalized versions.
662 647 """
663 648 return self._map.filefoldmapasdict()
664 649
665 650 def hastrackeddir(self, d):
666 651 return self._map.hastrackeddir(d)
667 652
668 653 def hasdir(self, d):
669 654 return self._map.hasdir(d)
670 655
671 656 @propertycache
672 657 def dirfoldmap(self):
673 658 f = {}
674 659 normcase = util.normcase
675 660 for name in self._map.tracked_dirs():
676 661 f[normcase(name)] = name
677 662 return f
678 663
679 664 ### code related to manipulation of entries and copy-sources
680 665
681 666 def set_tracked(self, f):
682 667 return self._map.set_tracked(f)
683 668
684 669 def set_untracked(self, f):
685 670 return self._map.set_untracked(f)
686 671
687 672 def set_clean(self, filename, mode, size, mtime):
688 673 self._map.set_clean(filename, mode, size, mtime)
689 674
690 675 def set_possibly_dirty(self, f):
691 676 self._map.set_possibly_dirty(f)
692 677
693 678 def reset_state(
694 679 self,
695 680 filename,
696 681 wc_tracked=False,
697 682 p1_tracked=False,
698 683 p2_info=False,
699 684 has_meaningful_mtime=True,
700 685 parentfiledata=None,
701 686 ):
702 687 return self._map.reset_state(
703 688 filename,
704 689 wc_tracked,
705 690 p1_tracked,
706 691 p2_info,
707 692 has_meaningful_mtime,
708 693 parentfiledata,
709 694 )
@@ -1,1404 +1,1439 b''
1 1 use bytes_cast::BytesCast;
2 2 use micro_timer::timed;
3 3 use std::borrow::Cow;
4 4 use std::path::PathBuf;
5 5
6 6 use super::on_disk;
7 7 use super::on_disk::DirstateV2ParseError;
8 8 use super::owning::OwningDirstateMap;
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::CopyMapIter;
14 14 use crate::dirstate::DirstateV2Data;
15 15 use crate::dirstate::ParentFileData;
16 16 use crate::dirstate::StateMapIter;
17 17 use crate::dirstate::TruncatedTimestamp;
18 18 use crate::matchers::Matcher;
19 19 use crate::utils::hg_path::{HgPath, HgPathBuf};
20 20 use crate::DirstateEntry;
21 21 use crate::DirstateError;
22 22 use crate::DirstateMapError;
23 23 use crate::DirstateParents;
24 24 use crate::DirstateStatus;
25 25 use crate::EntryState;
26 26 use crate::FastHashbrownMap as FastHashMap;
27 27 use crate::PatternFileWarning;
28 28 use crate::StatusError;
29 29 use crate::StatusOptions;
30 30
31 31 /// Append to an existing data file if the amount of unreachable data (not used
32 32 /// anymore) is less than this fraction of the total amount of existing data.
33 33 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
34 34
35 35 pub struct DirstateMap<'on_disk> {
36 36 /// Contents of the `.hg/dirstate` file
37 37 pub(super) on_disk: &'on_disk [u8],
38 38
39 39 pub(super) root: ChildNodes<'on_disk>,
40 40
41 41 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
42 42 pub(super) nodes_with_entry_count: u32,
43 43
44 44 /// Number of nodes anywhere in the tree that have
45 45 /// `.copy_source.is_some()`.
46 46 pub(super) nodes_with_copy_source_count: u32,
47 47
48 48 /// See on_disk::Header
49 49 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
50 50
51 51 /// How many bytes of `on_disk` are not used anymore
52 52 pub(super) unreachable_bytes: u32,
53 53 }
54 54
55 55 /// Using a plain `HgPathBuf` of the full path from the repository root as a
56 56 /// map key would also work: all paths in a given map have the same parent
57 57 /// path, so comparing full paths gives the same result as comparing base
58 58 /// names. However `HashMap` would waste time always re-hashing the same
59 59 /// string prefix.
60 60 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
61 61
62 62 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
63 63 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
64 64 pub(super) enum BorrowedPath<'tree, 'on_disk> {
65 65 InMemory(&'tree HgPathBuf),
66 66 OnDisk(&'on_disk HgPath),
67 67 }
68 68
69 69 pub(super) enum ChildNodes<'on_disk> {
70 70 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
71 71 OnDisk(&'on_disk [on_disk::Node]),
72 72 }
73 73
74 74 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
75 75 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
76 76 OnDisk(&'on_disk [on_disk::Node]),
77 77 }
78 78
79 79 pub(super) enum NodeRef<'tree, 'on_disk> {
80 80 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
81 81 OnDisk(&'on_disk on_disk::Node),
82 82 }
83 83
84 84 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
85 85 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
86 86 match *self {
87 87 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
88 88 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
89 89 }
90 90 }
91 91 }
92 92
93 93 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
94 94 type Target = HgPath;
95 95
96 96 fn deref(&self) -> &HgPath {
97 97 match *self {
98 98 BorrowedPath::InMemory(in_memory) => in_memory,
99 99 BorrowedPath::OnDisk(on_disk) => on_disk,
100 100 }
101 101 }
102 102 }
103 103
104 104 impl Default for ChildNodes<'_> {
105 105 fn default() -> Self {
106 106 ChildNodes::InMemory(Default::default())
107 107 }
108 108 }
109 109
110 110 impl<'on_disk> ChildNodes<'on_disk> {
111 111 pub(super) fn as_ref<'tree>(
112 112 &'tree self,
113 113 ) -> ChildNodesRef<'tree, 'on_disk> {
114 114 match self {
115 115 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
116 116 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
117 117 }
118 118 }
119 119
120 120 pub(super) fn is_empty(&self) -> bool {
121 121 match self {
122 122 ChildNodes::InMemory(nodes) => nodes.is_empty(),
123 123 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
124 124 }
125 125 }
126 126
127 127 fn make_mut(
128 128 &mut self,
129 129 on_disk: &'on_disk [u8],
130 130 unreachable_bytes: &mut u32,
131 131 ) -> Result<
132 132 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
133 133 DirstateV2ParseError,
134 134 > {
135 135 match self {
136 136 ChildNodes::InMemory(nodes) => Ok(nodes),
137 137 ChildNodes::OnDisk(nodes) => {
138 138 *unreachable_bytes +=
139 139 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
140 140 let nodes = nodes
141 141 .iter()
142 142 .map(|node| {
143 143 Ok((
144 144 node.path(on_disk)?,
145 145 node.to_in_memory_node(on_disk)?,
146 146 ))
147 147 })
148 148 .collect::<Result<_, _>>()?;
149 149 *self = ChildNodes::InMemory(nodes);
150 150 match self {
151 151 ChildNodes::InMemory(nodes) => Ok(nodes),
152 152 ChildNodes::OnDisk(_) => unreachable!(),
153 153 }
154 154 }
155 155 }
156 156 }
157 157 }
158 158
159 159 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
160 160 pub(super) fn get(
161 161 &self,
162 162 base_name: &HgPath,
163 163 on_disk: &'on_disk [u8],
164 164 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
165 165 match self {
166 166 ChildNodesRef::InMemory(nodes) => Ok(nodes
167 167 .get_key_value(base_name)
168 168 .map(|(k, v)| NodeRef::InMemory(k, v))),
169 169 ChildNodesRef::OnDisk(nodes) => {
170 170 let mut parse_result = Ok(());
171 171 let search_result = nodes.binary_search_by(|node| {
172 172 match node.base_name(on_disk) {
173 173 Ok(node_base_name) => node_base_name.cmp(base_name),
174 174 Err(e) => {
175 175 parse_result = Err(e);
176 176 // Dummy comparison result, `search_result` won’t
177 177 // be used since `parse_result` is an error
178 178 std::cmp::Ordering::Equal
179 179 }
180 180 }
181 181 });
182 182 parse_result.map(|()| {
183 183 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
184 184 })
185 185 }
186 186 }
187 187 }
188 188
189 189 /// Iterate in undefined order
190 190 pub(super) fn iter(
191 191 &self,
192 192 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
193 193 match self {
194 194 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
195 195 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
196 196 ),
197 197 ChildNodesRef::OnDisk(nodes) => {
198 198 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
199 199 }
200 200 }
201 201 }
202 202
203 203 /// Iterate in parallel in undefined order
204 204 pub(super) fn par_iter(
205 205 &self,
206 206 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
207 207 {
208 208 use rayon::prelude::*;
209 209 match self {
210 210 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
211 211 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
212 212 ),
213 213 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
214 214 nodes.par_iter().map(NodeRef::OnDisk),
215 215 ),
216 216 }
217 217 }
218 218
219 219 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
220 220 match self {
221 221 ChildNodesRef::InMemory(nodes) => {
222 222 let mut vec: Vec<_> = nodes
223 223 .iter()
224 224 .map(|(k, v)| NodeRef::InMemory(k, v))
225 225 .collect();
226 226 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
227 227 match node {
228 228 NodeRef::InMemory(path, _node) => path.base_name(),
229 229 NodeRef::OnDisk(_) => unreachable!(),
230 230 }
231 231 }
232 232 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
233 233 // value: https://github.com/rust-lang/rust/issues/34162
234 234 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
235 235 vec
236 236 }
237 237 ChildNodesRef::OnDisk(nodes) => {
238 238 // Nodes on disk are already sorted
239 239 nodes.iter().map(NodeRef::OnDisk).collect()
240 240 }
241 241 }
242 242 }
243 243 }
244 244
245 245 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
246 246 pub(super) fn full_path(
247 247 &self,
248 248 on_disk: &'on_disk [u8],
249 249 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
250 250 match self {
251 251 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
252 252 NodeRef::OnDisk(node) => node.full_path(on_disk),
253 253 }
254 254 }
255 255
256 256 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
257 257 /// HgPath>` detached from `'tree`
258 258 pub(super) fn full_path_borrowed(
259 259 &self,
260 260 on_disk: &'on_disk [u8],
261 261 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
262 262 match self {
263 263 NodeRef::InMemory(path, _node) => match path.full_path() {
264 264 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
265 265 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
266 266 },
267 267 NodeRef::OnDisk(node) => {
268 268 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
269 269 }
270 270 }
271 271 }
272 272
273 273 pub(super) fn base_name(
274 274 &self,
275 275 on_disk: &'on_disk [u8],
276 276 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
277 277 match self {
278 278 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
279 279 NodeRef::OnDisk(node) => node.base_name(on_disk),
280 280 }
281 281 }
282 282
283 283 pub(super) fn children(
284 284 &self,
285 285 on_disk: &'on_disk [u8],
286 286 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
287 287 match self {
288 288 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
289 289 NodeRef::OnDisk(node) => {
290 290 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
291 291 }
292 292 }
293 293 }
294 294
295 295 pub(super) fn has_copy_source(&self) -> bool {
296 296 match self {
297 297 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
298 298 NodeRef::OnDisk(node) => node.has_copy_source(),
299 299 }
300 300 }
301 301
302 302 pub(super) fn copy_source(
303 303 &self,
304 304 on_disk: &'on_disk [u8],
305 305 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
306 306 match self {
307 307 NodeRef::InMemory(_path, node) => {
308 308 Ok(node.copy_source.as_ref().map(|s| &**s))
309 309 }
310 310 NodeRef::OnDisk(node) => node.copy_source(on_disk),
311 311 }
312 312 }
313 313 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
314 314 /// HgPath>` detached from `'tree`
315 315 pub(super) fn copy_source_borrowed(
316 316 &self,
317 317 on_disk: &'on_disk [u8],
318 318 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
319 319 {
320 320 Ok(match self {
321 321 NodeRef::InMemory(_path, node) => {
322 322 node.copy_source.as_ref().map(|source| match source {
323 323 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
324 324 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
325 325 })
326 326 }
327 327 NodeRef::OnDisk(node) => node
328 328 .copy_source(on_disk)?
329 329 .map(|source| BorrowedPath::OnDisk(source)),
330 330 })
331 331 }
332 332
333 333 pub(super) fn entry(
334 334 &self,
335 335 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
336 336 match self {
337 337 NodeRef::InMemory(_path, node) => {
338 338 Ok(node.data.as_entry().copied())
339 339 }
340 340 NodeRef::OnDisk(node) => node.entry(),
341 341 }
342 342 }
343 343
344 344 pub(super) fn state(
345 345 &self,
346 346 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
347 347 Ok(self.entry()?.and_then(|e| {
348 348 if e.any_tracked() {
349 349 Some(e.state())
350 350 } else {
351 351 None
352 352 }
353 353 }))
354 354 }
355 355
356 356 pub(super) fn cached_directory_mtime(
357 357 &self,
358 358 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
359 359 match self {
360 360 NodeRef::InMemory(_path, node) => Ok(match node.data {
361 361 NodeData::CachedDirectory { mtime } => Some(mtime),
362 362 _ => None,
363 363 }),
364 364 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
365 365 }
366 366 }
367 367
368 368 pub(super) fn descendants_with_entry_count(&self) -> u32 {
369 369 match self {
370 370 NodeRef::InMemory(_path, node) => {
371 371 node.descendants_with_entry_count
372 372 }
373 373 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
374 374 }
375 375 }
376 376
377 377 pub(super) fn tracked_descendants_count(&self) -> u32 {
378 378 match self {
379 379 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
380 380 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
381 381 }
382 382 }
383 383 }
384 384
385 385 /// Represents a file or a directory
386 386 #[derive(Default)]
387 387 pub(super) struct Node<'on_disk> {
388 388 pub(super) data: NodeData,
389 389
390 390 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
391 391
392 392 pub(super) children: ChildNodes<'on_disk>,
393 393
394 394 /// How many (non-inclusive) descendants of this node have an entry.
395 395 pub(super) descendants_with_entry_count: u32,
396 396
397 397 /// How many (non-inclusive) descendants of this node have an entry whose
398 398 /// state is "tracked".
399 399 pub(super) tracked_descendants_count: u32,
400 400 }
401 401
402 402 pub(super) enum NodeData {
403 403 Entry(DirstateEntry),
404 404 CachedDirectory { mtime: TruncatedTimestamp },
405 405 None,
406 406 }
407 407
408 408 impl Default for NodeData {
409 409 fn default() -> Self {
410 410 NodeData::None
411 411 }
412 412 }
413 413
414 414 impl NodeData {
415 415 fn has_entry(&self) -> bool {
416 416 match self {
417 417 NodeData::Entry(_) => true,
418 418 _ => false,
419 419 }
420 420 }
421 421
422 422 fn as_entry(&self) -> Option<&DirstateEntry> {
423 423 match self {
424 424 NodeData::Entry(entry) => Some(entry),
425 425 _ => None,
426 426 }
427 427 }
428 428
429 429 fn as_entry_mut(&mut self) -> Option<&mut DirstateEntry> {
430 430 match self {
431 431 NodeData::Entry(entry) => Some(entry),
432 432 _ => None,
433 433 }
434 434 }
435 435 }
436 436
437 437 impl<'on_disk> DirstateMap<'on_disk> {
438 438 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
439 439 Self {
440 440 on_disk,
441 441 root: ChildNodes::default(),
442 442 nodes_with_entry_count: 0,
443 443 nodes_with_copy_source_count: 0,
444 444 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
445 445 unreachable_bytes: 0,
446 446 }
447 447 }
448 448
449 449 #[timed]
450 450 pub fn new_v2(
451 451 on_disk: &'on_disk [u8],
452 452 data_size: usize,
453 453 metadata: &[u8],
454 454 ) -> Result<Self, DirstateError> {
455 455 if let Some(data) = on_disk.get(..data_size) {
456 456 Ok(on_disk::read(data, metadata)?)
457 457 } else {
458 458 Err(DirstateV2ParseError.into())
459 459 }
460 460 }
461 461
462 462 #[timed]
463 463 pub fn new_v1(
464 464 on_disk: &'on_disk [u8],
465 465 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
466 466 let mut map = Self::empty(on_disk);
467 467 if map.on_disk.is_empty() {
468 468 return Ok((map, None));
469 469 }
470 470
471 471 let parents = parse_dirstate_entries(
472 472 map.on_disk,
473 473 |path, entry, copy_source| {
474 474 let tracked = entry.state().is_tracked();
475 475 let node = Self::get_or_insert_node(
476 476 map.on_disk,
477 477 &mut map.unreachable_bytes,
478 478 &mut map.root,
479 479 path,
480 480 WithBasename::to_cow_borrowed,
481 481 |ancestor| {
482 482 if tracked {
483 483 ancestor.tracked_descendants_count += 1
484 484 }
485 485 ancestor.descendants_with_entry_count += 1
486 486 },
487 487 )?;
488 488 assert!(
489 489 !node.data.has_entry(),
490 490 "duplicate dirstate entry in read"
491 491 );
492 492 assert!(
493 493 node.copy_source.is_none(),
494 494 "duplicate dirstate entry in read"
495 495 );
496 496 node.data = NodeData::Entry(*entry);
497 497 node.copy_source = copy_source.map(Cow::Borrowed);
498 498 map.nodes_with_entry_count += 1;
499 499 if copy_source.is_some() {
500 500 map.nodes_with_copy_source_count += 1
501 501 }
502 502 Ok(())
503 503 },
504 504 )?;
505 505 let parents = Some(parents.clone());
506 506
507 507 Ok((map, parents))
508 508 }
509 509
510 510 /// Assuming dirstate-v2 format, returns whether the next write should
511 511 /// append to the existing data file that contains `self.on_disk` (true),
512 512 /// or create a new data file from scratch (false).
513 513 pub(super) fn write_should_append(&self) -> bool {
514 514 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
515 515 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
516 516 }
517 517
518 518 fn get_node<'tree>(
519 519 &'tree self,
520 520 path: &HgPath,
521 521 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
522 522 let mut children = self.root.as_ref();
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.get(component, self.on_disk)? {
528 528 if let Some(next_component) = components.next() {
529 529 component = next_component;
530 530 children = child.children(self.on_disk)?;
531 531 } else {
532 532 return Ok(Some(child));
533 533 }
534 534 } else {
535 535 return Ok(None);
536 536 }
537 537 }
538 538 }
539 539
540 540 /// Returns a mutable reference to the node at `path` if it exists
541 541 ///
542 542 /// This takes `root` instead of `&mut self` so that callers can mutate
543 543 /// other fields while the returned borrow is still valid
544 544 fn get_node_mut<'tree>(
545 545 on_disk: &'on_disk [u8],
546 546 unreachable_bytes: &mut u32,
547 547 root: &'tree mut ChildNodes<'on_disk>,
548 548 path: &HgPath,
549 549 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
550 550 let mut children = root;
551 551 let mut components = path.components();
552 552 let mut component =
553 553 components.next().expect("expected at least one components");
554 554 loop {
555 555 if let Some(child) = children
556 556 .make_mut(on_disk, unreachable_bytes)?
557 557 .get_mut(component)
558 558 {
559 559 if let Some(next_component) = components.next() {
560 560 component = next_component;
561 561 children = &mut child.children;
562 562 } else {
563 563 return Ok(Some(child));
564 564 }
565 565 } else {
566 566 return Ok(None);
567 567 }
568 568 }
569 569 }
570 570
571 571 pub(super) fn get_or_insert<'tree, 'path>(
572 572 &'tree mut self,
573 573 path: &HgPath,
574 574 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
575 575 Self::get_or_insert_node(
576 576 self.on_disk,
577 577 &mut self.unreachable_bytes,
578 578 &mut self.root,
579 579 path,
580 580 WithBasename::to_cow_owned,
581 581 |_| {},
582 582 )
583 583 }
584 584
585 585 fn get_or_insert_node<'tree, 'path>(
586 586 on_disk: &'on_disk [u8],
587 587 unreachable_bytes: &mut u32,
588 588 root: &'tree mut ChildNodes<'on_disk>,
589 589 path: &'path HgPath,
590 590 to_cow: impl Fn(
591 591 WithBasename<&'path HgPath>,
592 592 ) -> WithBasename<Cow<'on_disk, HgPath>>,
593 593 mut each_ancestor: impl FnMut(&mut Node),
594 594 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
595 595 let mut child_nodes = root;
596 596 let mut inclusive_ancestor_paths =
597 597 WithBasename::inclusive_ancestors_of(path);
598 598 let mut ancestor_path = inclusive_ancestor_paths
599 599 .next()
600 600 .expect("expected at least one inclusive ancestor");
601 601 loop {
602 602 let (_, child_node) = child_nodes
603 603 .make_mut(on_disk, unreachable_bytes)?
604 604 .raw_entry_mut()
605 605 .from_key(ancestor_path.base_name())
606 606 .or_insert_with(|| (to_cow(ancestor_path), Node::default()));
607 607 if let Some(next) = inclusive_ancestor_paths.next() {
608 608 each_ancestor(child_node);
609 609 ancestor_path = next;
610 610 child_nodes = &mut child_node.children;
611 611 } else {
612 612 return Ok(child_node);
613 613 }
614 614 }
615 615 }
616 616
617 617 fn reset_state(
618 618 &mut self,
619 619 filename: &HgPath,
620 620 old_entry_opt: Option<DirstateEntry>,
621 621 wc_tracked: bool,
622 622 p1_tracked: bool,
623 623 p2_info: bool,
624 624 has_meaningful_mtime: bool,
625 625 parent_file_data_opt: Option<ParentFileData>,
626 626 ) -> Result<(), DirstateError> {
627 627 let (had_entry, was_tracked) = match old_entry_opt {
628 628 Some(old_entry) => (true, old_entry.tracked()),
629 629 None => (false, false),
630 630 };
631 631 let node = Self::get_or_insert_node(
632 632 self.on_disk,
633 633 &mut self.unreachable_bytes,
634 634 &mut self.root,
635 635 filename,
636 636 WithBasename::to_cow_owned,
637 637 |ancestor| {
638 638 if !had_entry {
639 639 ancestor.descendants_with_entry_count += 1;
640 640 }
641 641 if was_tracked {
642 642 if !wc_tracked {
643 643 ancestor.tracked_descendants_count = ancestor
644 644 .tracked_descendants_count
645 645 .checked_sub(1)
646 646 .expect("tracked count to be >= 0");
647 647 }
648 648 } else {
649 649 if wc_tracked {
650 650 ancestor.tracked_descendants_count += 1;
651 651 }
652 652 }
653 653 },
654 654 )?;
655 655
656 656 let v2_data = if let Some(parent_file_data) = parent_file_data_opt {
657 657 DirstateV2Data {
658 658 wc_tracked,
659 659 p1_tracked,
660 660 p2_info,
661 661 mode_size: parent_file_data.mode_size,
662 662 mtime: if has_meaningful_mtime {
663 663 parent_file_data.mtime
664 664 } else {
665 665 None
666 666 },
667 667 ..Default::default()
668 668 }
669 669 } else {
670 670 DirstateV2Data {
671 671 wc_tracked,
672 672 p1_tracked,
673 673 p2_info,
674 674 ..Default::default()
675 675 }
676 676 };
677 677 if !had_entry {
678 678 self.nodes_with_entry_count += 1;
679 679 }
680 680 node.data = NodeData::Entry(DirstateEntry::from_v2_data(v2_data));
681 681 Ok(())
682 682 }
683 683
684 684 fn set_tracked(
685 685 &mut self,
686 686 filename: &HgPath,
687 687 old_entry_opt: Option<DirstateEntry>,
688 688 ) -> Result<bool, DirstateV2ParseError> {
689 689 let was_tracked = old_entry_opt.map_or(false, |e| e.tracked());
690 690 let had_entry = old_entry_opt.is_some();
691 691 let tracked_count_increment = if was_tracked { 0 } else { 1 };
692 692 let mut new = false;
693 693
694 694 let node = Self::get_or_insert_node(
695 695 self.on_disk,
696 696 &mut self.unreachable_bytes,
697 697 &mut self.root,
698 698 filename,
699 699 WithBasename::to_cow_owned,
700 700 |ancestor| {
701 701 if !had_entry {
702 702 ancestor.descendants_with_entry_count += 1;
703 703 }
704 704
705 705 ancestor.tracked_descendants_count += tracked_count_increment;
706 706 },
707 707 )?;
708 708 let new_entry = if let Some(old_entry) = old_entry_opt {
709 709 let mut e = old_entry.clone();
710 710 if e.tracked() {
711 711 // XXX
712 712 // This is probably overkill for more case, but we need this to
713 713 // fully replace the `normallookup` call with `set_tracked`
714 714 // one. Consider smoothing this in the future.
715 715 e.set_possibly_dirty();
716 716 } else {
717 717 new = true;
718 718 e.set_tracked();
719 719 }
720 720 e
721 721 } else {
722 722 self.nodes_with_entry_count += 1;
723 723 new = true;
724 724 DirstateEntry::new_tracked()
725 725 };
726 726 node.data = NodeData::Entry(new_entry);
727 727 Ok(new)
728 728 }
729 729
730 730 /// It is the responsibility of the caller to know that there was an entry
731 731 /// there before. Does not handle the removal of copy source
732 732 fn set_untracked(
733 733 &mut self,
734 734 filename: &HgPath,
735 735 old_entry: DirstateEntry,
736 736 ) -> Result<(), DirstateV2ParseError> {
737 737 let node = Self::get_or_insert_node(
738 738 self.on_disk,
739 739 &mut self.unreachable_bytes,
740 740 &mut self.root,
741 741 filename,
742 742 WithBasename::to_cow_owned,
743 743 |ancestor| {
744 744 ancestor.tracked_descendants_count = ancestor
745 745 .tracked_descendants_count
746 746 .checked_sub(1)
747 747 .expect("tracked_descendants_count should be >= 0");
748 748 },
749 749 )?;
750 750 let mut new_entry = old_entry.clone();
751 751 new_entry.set_untracked();
752 752 node.data = NodeData::Entry(new_entry);
753 753 Ok(())
754 754 }
755 755
756 756 fn set_clean(
757 757 &mut self,
758 758 filename: &HgPath,
759 759 old_entry: DirstateEntry,
760 760 mode: u32,
761 761 size: u32,
762 762 mtime: TruncatedTimestamp,
763 763 ) -> Result<(), DirstateError> {
764 764 let node = Self::get_or_insert_node(
765 765 self.on_disk,
766 766 &mut self.unreachable_bytes,
767 767 &mut self.root,
768 768 filename,
769 769 WithBasename::to_cow_owned,
770 770 |ancestor| {
771 771 if !old_entry.tracked() {
772 772 ancestor.tracked_descendants_count += 1;
773 773 }
774 774 },
775 775 )?;
776 776 let mut new_entry = old_entry.clone();
777 777 new_entry.set_clean(mode, size, mtime);
778 778 node.data = NodeData::Entry(new_entry);
779 779 Ok(())
780 780 }
781 781
782 782 fn set_possibly_dirty(
783 783 &mut self,
784 784 filename: &HgPath,
785 785 ) -> Result<(), DirstateError> {
786 786 let node = Self::get_or_insert_node(
787 787 self.on_disk,
788 788 &mut self.unreachable_bytes,
789 789 &mut self.root,
790 790 filename,
791 791 WithBasename::to_cow_owned,
792 792 |_ancestor| {},
793 793 )?;
794 794 let entry = node.data.as_entry_mut().expect("entry should exist");
795 795 entry.set_possibly_dirty();
796 796 node.data = NodeData::Entry(*entry);
797 797 Ok(())
798 798 }
799 799
800 800 fn iter_nodes<'tree>(
801 801 &'tree self,
802 802 ) -> impl Iterator<
803 803 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
804 804 > + 'tree {
805 805 // Depth first tree traversal.
806 806 //
807 807 // If we could afford internal iteration and recursion,
808 808 // this would look like:
809 809 //
810 810 // ```
811 811 // fn traverse_children(
812 812 // children: &ChildNodes,
813 813 // each: &mut impl FnMut(&Node),
814 814 // ) {
815 815 // for child in children.values() {
816 816 // traverse_children(&child.children, each);
817 817 // each(child);
818 818 // }
819 819 // }
820 820 // ```
821 821 //
822 822 // However we want an external iterator and therefore can’t use the
823 823 // call stack. Use an explicit stack instead:
824 824 let mut stack = Vec::new();
825 825 let mut iter = self.root.as_ref().iter();
826 826 std::iter::from_fn(move || {
827 827 while let Some(child_node) = iter.next() {
828 828 let children = match child_node.children(self.on_disk) {
829 829 Ok(children) => children,
830 830 Err(error) => return Some(Err(error)),
831 831 };
832 832 // Pseudo-recursion
833 833 let new_iter = children.iter();
834 834 let old_iter = std::mem::replace(&mut iter, new_iter);
835 835 stack.push((child_node, old_iter));
836 836 }
837 837 // Found the end of a `children.iter()` iterator.
838 838 if let Some((child_node, next_iter)) = stack.pop() {
839 839 // "Return" from pseudo-recursion by restoring state from the
840 840 // explicit stack
841 841 iter = next_iter;
842 842
843 843 Some(Ok(child_node))
844 844 } else {
845 845 // Reached the bottom of the stack, we’re done
846 846 None
847 847 }
848 848 })
849 849 }
850 850
851 851 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
852 852 if let Cow::Borrowed(path) = path {
853 853 *unreachable_bytes += path.len() as u32
854 854 }
855 855 }
856 856 }
857 857
858 858 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
859 859 ///
860 860 /// The callback is only called for incoming `Ok` values. Errors are passed
861 861 /// through as-is. In order to let it use the `?` operator the callback is
862 862 /// expected to return a `Result` of `Option`, instead of an `Option` of
863 863 /// `Result`.
864 864 fn filter_map_results<'a, I, F, A, B, E>(
865 865 iter: I,
866 866 f: F,
867 867 ) -> impl Iterator<Item = Result<B, E>> + 'a
868 868 where
869 869 I: Iterator<Item = Result<A, E>> + 'a,
870 870 F: Fn(A) -> Result<Option<B>, E> + 'a,
871 871 {
872 872 iter.filter_map(move |result| match result {
873 873 Ok(node) => f(node).transpose(),
874 874 Err(e) => Some(Err(e)),
875 875 })
876 876 }
877 877
878 878 impl OwningDirstateMap {
879 879 pub fn clear(&mut self) {
880 880 self.with_dmap_mut(|map| {
881 881 map.root = Default::default();
882 882 map.nodes_with_entry_count = 0;
883 883 map.nodes_with_copy_source_count = 0;
884 884 });
885 885 }
886 886
887 887 pub fn set_entry(
888 888 &mut self,
889 889 filename: &HgPath,
890 890 entry: DirstateEntry,
891 891 ) -> Result<(), DirstateV2ParseError> {
892 892 self.with_dmap_mut(|map| {
893 893 map.get_or_insert(&filename)?.data = NodeData::Entry(entry);
894 894 Ok(())
895 895 })
896 896 }
897 897
898 898 pub fn set_tracked(
899 899 &mut self,
900 900 filename: &HgPath,
901 901 ) -> Result<bool, DirstateV2ParseError> {
902 902 let old_entry_opt = self.get(filename)?;
903 903 self.with_dmap_mut(|map| map.set_tracked(filename, old_entry_opt))
904 904 }
905 905
906 906 pub fn set_untracked(
907 907 &mut self,
908 908 filename: &HgPath,
909 909 ) -> Result<bool, DirstateError> {
910 910 let old_entry_opt = self.get(filename)?;
911 911 match old_entry_opt {
912 912 None => Ok(false),
913 913 Some(old_entry) => {
914 914 if !old_entry.tracked() {
915 915 // `DirstateMap::set_untracked` is not a noop if
916 916 // already not tracked as it will decrement the
917 917 // tracked counters while going down.
918 918 return Ok(true);
919 919 }
920 920 if old_entry.added() {
921 921 // Untracking an "added" entry will just result in a
922 922 // worthless entry (and other parts of the code will
923 923 // complain about it), just drop it entirely.
924 924 self.drop_entry_and_copy_source(filename)?;
925 925 return Ok(true);
926 926 }
927 927 if !old_entry.p2_info() {
928 928 self.copy_map_remove(filename)?;
929 929 }
930 930
931 931 self.with_dmap_mut(|map| {
932 932 map.set_untracked(filename, old_entry)?;
933 933 Ok(true)
934 934 })
935 935 }
936 936 }
937 937 }
938 938
939 939 pub fn set_clean(
940 940 &mut self,
941 941 filename: &HgPath,
942 942 mode: u32,
943 943 size: u32,
944 944 mtime: TruncatedTimestamp,
945 945 ) -> Result<(), DirstateError> {
946 946 let old_entry = match self.get(filename)? {
947 947 None => {
948 948 return Err(
949 949 DirstateMapError::PathNotFound(filename.into()).into()
950 950 )
951 951 }
952 952 Some(e) => e,
953 953 };
954 954 self.copy_map_remove(filename)?;
955 955 self.with_dmap_mut(|map| {
956 956 map.set_clean(filename, old_entry, mode, size, mtime)
957 957 })
958 958 }
959 959
960 960 pub fn set_possibly_dirty(
961 961 &mut self,
962 962 filename: &HgPath,
963 963 ) -> Result<(), DirstateError> {
964 964 if self.get(filename)?.is_none() {
965 965 return Err(DirstateMapError::PathNotFound(filename.into()).into());
966 966 }
967 967 self.with_dmap_mut(|map| map.set_possibly_dirty(filename))
968 968 }
969 969
970 970 pub fn reset_state(
971 971 &mut self,
972 972 filename: &HgPath,
973 973 wc_tracked: bool,
974 974 p1_tracked: bool,
975 975 p2_info: bool,
976 976 has_meaningful_mtime: bool,
977 977 parent_file_data_opt: Option<ParentFileData>,
978 978 ) -> Result<(), DirstateError> {
979 979 if !(p1_tracked || p2_info || wc_tracked) {
980 980 self.drop_entry_and_copy_source(filename)?;
981 981 return Ok(());
982 982 }
983 983 self.copy_map_remove(filename)?;
984 984 let old_entry_opt = self.get(filename)?;
985 985 self.with_dmap_mut(|map| {
986 986 map.reset_state(
987 987 filename,
988 988 old_entry_opt,
989 989 wc_tracked,
990 990 p1_tracked,
991 991 p2_info,
992 992 has_meaningful_mtime,
993 993 parent_file_data_opt,
994 994 )
995 995 })
996 996 }
997 997
998 998 pub fn drop_entry_and_copy_source(
999 999 &mut self,
1000 1000 filename: &HgPath,
1001 1001 ) -> Result<(), DirstateError> {
1002 1002 let was_tracked = self
1003 1003 .get(filename)?
1004 1004 .map_or(false, |e| e.state().is_tracked());
1005 1005 struct Dropped {
1006 1006 was_tracked: bool,
1007 1007 had_entry: bool,
1008 1008 had_copy_source: bool,
1009 1009 }
1010 1010
1011 1011 /// If this returns `Ok(Some((dropped, removed)))`, then
1012 1012 ///
1013 1013 /// * `dropped` is about the leaf node that was at `filename`
1014 1014 /// * `removed` is whether this particular level of recursion just
1015 1015 /// removed a node in `nodes`.
1016 1016 fn recur<'on_disk>(
1017 1017 on_disk: &'on_disk [u8],
1018 1018 unreachable_bytes: &mut u32,
1019 1019 nodes: &mut ChildNodes<'on_disk>,
1020 1020 path: &HgPath,
1021 1021 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
1022 1022 let (first_path_component, rest_of_path) =
1023 1023 path.split_first_component();
1024 1024 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
1025 1025 let node = if let Some(node) = nodes.get_mut(first_path_component)
1026 1026 {
1027 1027 node
1028 1028 } else {
1029 1029 return Ok(None);
1030 1030 };
1031 1031 let dropped;
1032 1032 if let Some(rest) = rest_of_path {
1033 1033 if let Some((d, removed)) = recur(
1034 1034 on_disk,
1035 1035 unreachable_bytes,
1036 1036 &mut node.children,
1037 1037 rest,
1038 1038 )? {
1039 1039 dropped = d;
1040 1040 if dropped.had_entry {
1041 1041 node.descendants_with_entry_count = node
1042 1042 .descendants_with_entry_count
1043 1043 .checked_sub(1)
1044 1044 .expect(
1045 1045 "descendants_with_entry_count should be >= 0",
1046 1046 );
1047 1047 }
1048 1048 if dropped.was_tracked {
1049 1049 node.tracked_descendants_count = node
1050 1050 .tracked_descendants_count
1051 1051 .checked_sub(1)
1052 1052 .expect(
1053 1053 "tracked_descendants_count should be >= 0",
1054 1054 );
1055 1055 }
1056 1056
1057 1057 // Directory caches must be invalidated when removing a
1058 1058 // child node
1059 1059 if removed {
1060 1060 if let NodeData::CachedDirectory { .. } = &node.data {
1061 1061 node.data = NodeData::None
1062 1062 }
1063 1063 }
1064 1064 } else {
1065 1065 return Ok(None);
1066 1066 }
1067 1067 } else {
1068 1068 let entry = node.data.as_entry();
1069 1069 let was_tracked = entry.map_or(false, |entry| entry.tracked());
1070 1070 let had_entry = entry.is_some();
1071 1071 if had_entry {
1072 1072 node.data = NodeData::None
1073 1073 }
1074 1074 let mut had_copy_source = false;
1075 1075 if let Some(source) = &node.copy_source {
1076 1076 DirstateMap::count_dropped_path(unreachable_bytes, source);
1077 1077 had_copy_source = true;
1078 1078 node.copy_source = None
1079 1079 }
1080 1080 dropped = Dropped {
1081 1081 was_tracked,
1082 1082 had_entry,
1083 1083 had_copy_source,
1084 1084 };
1085 1085 }
1086 1086 // After recursion, for both leaf (rest_of_path is None) nodes and
1087 1087 // parent nodes, remove a node if it just became empty.
1088 1088 let remove = !node.data.has_entry()
1089 1089 && node.copy_source.is_none()
1090 1090 && node.children.is_empty();
1091 1091 if remove {
1092 1092 let (key, _) =
1093 1093 nodes.remove_entry(first_path_component).unwrap();
1094 1094 DirstateMap::count_dropped_path(
1095 1095 unreachable_bytes,
1096 1096 key.full_path(),
1097 1097 )
1098 1098 }
1099 1099 Ok(Some((dropped, remove)))
1100 1100 }
1101 1101
1102 1102 self.with_dmap_mut(|map| {
1103 1103 if let Some((dropped, _removed)) = recur(
1104 1104 map.on_disk,
1105 1105 &mut map.unreachable_bytes,
1106 1106 &mut map.root,
1107 1107 filename,
1108 1108 )? {
1109 1109 if dropped.had_entry {
1110 1110 map.nodes_with_entry_count = map
1111 1111 .nodes_with_entry_count
1112 1112 .checked_sub(1)
1113 1113 .expect("nodes_with_entry_count should be >= 0");
1114 1114 }
1115 1115 if dropped.had_copy_source {
1116 1116 map.nodes_with_copy_source_count = map
1117 1117 .nodes_with_copy_source_count
1118 1118 .checked_sub(1)
1119 1119 .expect("nodes_with_copy_source_count should be >= 0");
1120 1120 }
1121 1121 } else {
1122 1122 debug_assert!(!was_tracked);
1123 1123 }
1124 1124 Ok(())
1125 1125 })
1126 1126 }
1127 1127
1128 1128 pub fn has_tracked_dir(
1129 1129 &mut self,
1130 1130 directory: &HgPath,
1131 1131 ) -> Result<bool, DirstateError> {
1132 1132 self.with_dmap_mut(|map| {
1133 1133 if let Some(node) = map.get_node(directory)? {
1134 1134 // A node without a `DirstateEntry` was created to hold child
1135 1135 // nodes, and is therefore a directory.
1136 1136 let state = node.state()?;
1137 1137 Ok(state.is_none() && node.tracked_descendants_count() > 0)
1138 1138 } else {
1139 1139 Ok(false)
1140 1140 }
1141 1141 })
1142 1142 }
1143 1143
1144 1144 pub fn has_dir(
1145 1145 &mut self,
1146 1146 directory: &HgPath,
1147 1147 ) -> Result<bool, DirstateError> {
1148 1148 self.with_dmap_mut(|map| {
1149 1149 if let Some(node) = map.get_node(directory)? {
1150 1150 // A node without a `DirstateEntry` was created to hold child
1151 1151 // nodes, and is therefore a directory.
1152 1152 let state = node.state()?;
1153 1153 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
1154 1154 } else {
1155 1155 Ok(false)
1156 1156 }
1157 1157 })
1158 1158 }
1159 1159
1160 1160 #[timed]
1161 1161 pub fn pack_v1(
1162 1162 &self,
1163 1163 parents: DirstateParents,
1164 1164 ) -> Result<Vec<u8>, DirstateError> {
1165 1165 let map = self.get_map();
1166 1166 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1167 1167 // reallocations
1168 1168 let mut size = parents.as_bytes().len();
1169 1169 for node in map.iter_nodes() {
1170 1170 let node = node?;
1171 1171 if node.entry()?.is_some() {
1172 1172 size += packed_entry_size(
1173 1173 node.full_path(map.on_disk)?,
1174 1174 node.copy_source(map.on_disk)?,
1175 1175 );
1176 1176 }
1177 1177 }
1178 1178
1179 1179 let mut packed = Vec::with_capacity(size);
1180 1180 packed.extend(parents.as_bytes());
1181 1181
1182 1182 for node in map.iter_nodes() {
1183 1183 let node = node?;
1184 1184 if let Some(entry) = node.entry()? {
1185 1185 pack_entry(
1186 1186 node.full_path(map.on_disk)?,
1187 1187 &entry,
1188 1188 node.copy_source(map.on_disk)?,
1189 1189 &mut packed,
1190 1190 );
1191 1191 }
1192 1192 }
1193 1193 Ok(packed)
1194 1194 }
1195 1195
1196 1196 /// Returns new data and metadata together with whether that data should be
1197 1197 /// appended to the existing data file whose content is at
1198 1198 /// `map.on_disk` (true), instead of written to a new data file
1199 1199 /// (false).
1200 1200 #[timed]
1201 1201 pub fn pack_v2(
1202 1202 &self,
1203 1203 can_append: bool,
1204 1204 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool), DirstateError> {
1205 1205 let map = self.get_map();
1206 1206 on_disk::write(map, can_append)
1207 1207 }
1208 1208
1209 1209 /// `callback` allows the caller to process and do something with the
1210 1210 /// results of the status. This is needed to do so efficiently (i.e.
1211 1211 /// without cloning the `DirstateStatus` object with its paths) because
1212 1212 /// we need to borrow from `Self`.
1213 1213 pub fn with_status<R>(
1214 1214 &mut self,
1215 1215 matcher: &(dyn Matcher + Sync),
1216 1216 root_dir: PathBuf,
1217 1217 ignore_files: Vec<PathBuf>,
1218 1218 options: StatusOptions,
1219 1219 callback: impl for<'r> FnOnce(
1220 1220 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
1221 1221 ) -> R,
1222 1222 ) -> R {
1223 1223 self.with_dmap_mut(|map| {
1224 1224 callback(super::status::status(
1225 1225 map,
1226 1226 matcher,
1227 1227 root_dir,
1228 1228 ignore_files,
1229 1229 options,
1230 1230 ))
1231 1231 })
1232 1232 }
1233 1233
1234 1234 pub fn copy_map_len(&self) -> usize {
1235 1235 let map = self.get_map();
1236 1236 map.nodes_with_copy_source_count as usize
1237 1237 }
1238 1238
1239 1239 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1240 1240 let map = self.get_map();
1241 1241 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1242 1242 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1243 1243 Some((node.full_path(map.on_disk)?, source))
1244 1244 } else {
1245 1245 None
1246 1246 })
1247 1247 }))
1248 1248 }
1249 1249
1250 1250 pub fn copy_map_contains_key(
1251 1251 &self,
1252 1252 key: &HgPath,
1253 1253 ) -> Result<bool, DirstateV2ParseError> {
1254 1254 let map = self.get_map();
1255 1255 Ok(if let Some(node) = map.get_node(key)? {
1256 1256 node.has_copy_source()
1257 1257 } else {
1258 1258 false
1259 1259 })
1260 1260 }
1261 1261
1262 1262 pub fn copy_map_get(
1263 1263 &self,
1264 1264 key: &HgPath,
1265 1265 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1266 1266 let map = self.get_map();
1267 1267 if let Some(node) = map.get_node(key)? {
1268 1268 if let Some(source) = node.copy_source(map.on_disk)? {
1269 1269 return Ok(Some(source));
1270 1270 }
1271 1271 }
1272 1272 Ok(None)
1273 1273 }
1274 1274
1275 1275 pub fn copy_map_remove(
1276 1276 &mut self,
1277 1277 key: &HgPath,
1278 1278 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1279 1279 self.with_dmap_mut(|map| {
1280 1280 let count = &mut map.nodes_with_copy_source_count;
1281 1281 let unreachable_bytes = &mut map.unreachable_bytes;
1282 1282 Ok(DirstateMap::get_node_mut(
1283 1283 map.on_disk,
1284 1284 unreachable_bytes,
1285 1285 &mut map.root,
1286 1286 key,
1287 1287 )?
1288 1288 .and_then(|node| {
1289 1289 if let Some(source) = &node.copy_source {
1290 1290 *count -= 1;
1291 1291 DirstateMap::count_dropped_path(unreachable_bytes, source);
1292 1292 }
1293 1293 node.copy_source.take().map(Cow::into_owned)
1294 1294 }))
1295 1295 })
1296 1296 }
1297 1297
1298 1298 pub fn copy_map_insert(
1299 1299 &mut self,
1300 1300 key: HgPathBuf,
1301 1301 value: HgPathBuf,
1302 1302 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1303 1303 self.with_dmap_mut(|map| {
1304 1304 let node = DirstateMap::get_or_insert_node(
1305 1305 map.on_disk,
1306 1306 &mut map.unreachable_bytes,
1307 1307 &mut map.root,
1308 1308 &key,
1309 1309 WithBasename::to_cow_owned,
1310 1310 |_ancestor| {},
1311 1311 )?;
1312 1312 if node.copy_source.is_none() {
1313 1313 map.nodes_with_copy_source_count += 1
1314 1314 }
1315 1315 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1316 1316 })
1317 1317 }
1318 1318
1319 1319 pub fn len(&self) -> usize {
1320 1320 let map = self.get_map();
1321 1321 map.nodes_with_entry_count as usize
1322 1322 }
1323 1323
1324 1324 pub fn contains_key(
1325 1325 &self,
1326 1326 key: &HgPath,
1327 1327 ) -> Result<bool, DirstateV2ParseError> {
1328 1328 Ok(self.get(key)?.is_some())
1329 1329 }
1330 1330
1331 1331 pub fn get(
1332 1332 &self,
1333 1333 key: &HgPath,
1334 1334 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1335 1335 let map = self.get_map();
1336 1336 Ok(if let Some(node) = map.get_node(key)? {
1337 1337 node.entry()?
1338 1338 } else {
1339 1339 None
1340 1340 })
1341 1341 }
1342 1342
1343 1343 pub fn iter(&self) -> StateMapIter<'_> {
1344 1344 let map = self.get_map();
1345 1345 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1346 1346 Ok(if let Some(entry) = node.entry()? {
1347 1347 Some((node.full_path(map.on_disk)?, entry))
1348 1348 } else {
1349 1349 None
1350 1350 })
1351 1351 }))
1352 1352 }
1353 1353
1354 1354 pub fn iter_tracked_dirs(
1355 1355 &mut self,
1356 1356 ) -> Result<
1357 1357 Box<
1358 1358 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1359 1359 + Send
1360 1360 + '_,
1361 1361 >,
1362 1362 DirstateError,
1363 1363 > {
1364 1364 let map = self.get_map();
1365 1365 let on_disk = map.on_disk;
1366 1366 Ok(Box::new(filter_map_results(
1367 1367 map.iter_nodes(),
1368 1368 move |node| {
1369 1369 Ok(if node.tracked_descendants_count() > 0 {
1370 1370 Some(node.full_path(on_disk)?)
1371 1371 } else {
1372 1372 None
1373 1373 })
1374 1374 },
1375 1375 )))
1376 1376 }
1377 1377
1378 /// Only public because it needs to be exposed to the Python layer.
1379 /// It is not the full `setparents` logic, only the parts that mutate the
1380 /// entries.
1381 pub fn setparents_fixup(
1382 &mut self,
1383 ) -> Result<Vec<(HgPathBuf, HgPathBuf)>, DirstateV2ParseError> {
1384 // XXX
1385 // All the copying and re-querying is quite inefficient, but this is
1386 // still a lot better than doing it from Python.
1387 //
1388 // The better solution is to develop a mechanism for `iter_mut`,
1389 // which will be a lot more involved: we're dealing with a lazy,
1390 // append-mostly, tree-like data structure. This will do for now.
1391 let mut copies = vec![];
1392 let mut files_with_p2_info = vec![];
1393 for res in self.iter() {
1394 let (path, entry) = res?;
1395 if entry.p2_info() {
1396 files_with_p2_info.push(path.to_owned())
1397 }
1398 }
1399 self.with_dmap_mut(|map| {
1400 for path in files_with_p2_info.iter() {
1401 let node = map.get_or_insert(path)?;
1402 let entry =
1403 node.data.as_entry_mut().expect("entry should exist");
1404 entry.drop_merge_data();
1405 if let Some(source) = node.copy_source.take().as_deref() {
1406 copies.push((path.to_owned(), source.to_owned()));
1407 }
1408 }
1409 Ok(copies)
1410 })
1411 }
1412
1378 1413 pub fn debug_iter(
1379 1414 &self,
1380 1415 all: bool,
1381 1416 ) -> Box<
1382 1417 dyn Iterator<
1383 1418 Item = Result<
1384 1419 (&HgPath, (u8, i32, i32, i32)),
1385 1420 DirstateV2ParseError,
1386 1421 >,
1387 1422 > + Send
1388 1423 + '_,
1389 1424 > {
1390 1425 let map = self.get_map();
1391 1426 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1392 1427 let debug_tuple = if let Some(entry) = node.entry()? {
1393 1428 entry.debug_tuple()
1394 1429 } else if !all {
1395 1430 return Ok(None);
1396 1431 } else if let Some(mtime) = node.cached_directory_mtime()? {
1397 1432 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1398 1433 } else {
1399 1434 (b' ', 0, -1, -1)
1400 1435 };
1401 1436 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1402 1437 }))
1403 1438 }
1404 1439 }
@@ -1,559 +1,572 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 15 exc, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, PyNone, PyObject,
16 16 PyResult, Python, PythonObject, ToPyObject, UnsafePyLeaked,
17 17 };
18 18 use hg::dirstate::{ParentFileData, TruncatedTimestamp};
19 19
20 20 use crate::{
21 21 dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
22 22 dirstate::item::DirstateItem,
23 23 pybytes_deref::PyBytesDeref,
24 24 };
25 25 use hg::{
26 26 dirstate::StateMapIter,
27 27 dirstate_tree::on_disk::DirstateV2ParseError,
28 28 dirstate_tree::owning::OwningDirstateMap,
29 29 revlog::Node,
30 30 utils::files::normalize_case,
31 31 utils::hg_path::{HgPath, HgPathBuf},
32 32 DirstateEntry, DirstateError, DirstateParents, EntryState,
33 33 };
34 34
35 35 // TODO
36 36 // This object needs to share references to multiple members of its Rust
37 37 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
38 38 // Right now `CopyMap` is done, but it needs to have an explicit reference
39 39 // to `RustDirstateMap` which itself needs to have an encapsulation for
40 40 // every method in `CopyMap` (copymapcopy, etc.).
41 41 // This is ugly and hard to maintain.
42 42 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
43 43 // `py_class!` is already implemented and does not mention
44 44 // `RustDirstateMap`, rightfully so.
45 45 // All attributes also have to have a separate refcount data attribute for
46 46 // leaks, with all methods that go along for reference sharing.
47 47 py_class!(pub class DirstateMap |py| {
48 48 @shared data inner: OwningDirstateMap;
49 49
50 50 /// Returns a `(dirstate_map, parents)` tuple
51 51 @staticmethod
52 52 def new_v1(
53 53 on_disk: PyBytes,
54 54 ) -> PyResult<PyObject> {
55 55 let on_disk = PyBytesDeref::new(py, on_disk);
56 56 let (map, parents) = OwningDirstateMap::new_v1(on_disk)
57 57 .map_err(|e| dirstate_error(py, e))?;
58 58 let map = Self::create_instance(py, map)?;
59 59 let p1 = PyBytes::new(py, parents.p1.as_bytes());
60 60 let p2 = PyBytes::new(py, parents.p2.as_bytes());
61 61 let parents = (p1, p2);
62 62 Ok((map, parents).to_py_object(py).into_object())
63 63 }
64 64
65 65 /// Returns a DirstateMap
66 66 @staticmethod
67 67 def new_v2(
68 68 on_disk: PyBytes,
69 69 data_size: usize,
70 70 tree_metadata: PyBytes,
71 71 ) -> PyResult<PyObject> {
72 72 let dirstate_error = |e: DirstateError| {
73 73 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
74 74 };
75 75 let on_disk = PyBytesDeref::new(py, on_disk);
76 76 let map = OwningDirstateMap::new_v2(
77 77 on_disk, data_size, tree_metadata.data(py),
78 78 ).map_err(dirstate_error)?;
79 79 let map = Self::create_instance(py, map)?;
80 80 Ok(map.into_object())
81 81 }
82 82
83 83 def clear(&self) -> PyResult<PyObject> {
84 84 self.inner(py).borrow_mut().clear();
85 85 Ok(py.None())
86 86 }
87 87
88 88 def get(
89 89 &self,
90 90 key: PyObject,
91 91 default: Option<PyObject> = None
92 92 ) -> PyResult<Option<PyObject>> {
93 93 let key = key.extract::<PyBytes>(py)?;
94 94 match self
95 95 .inner(py)
96 96 .borrow()
97 97 .get(HgPath::new(key.data(py)))
98 98 .map_err(|e| v2_error(py, e))?
99 99 {
100 100 Some(entry) => {
101 101 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
102 102 },
103 103 None => Ok(default)
104 104 }
105 105 }
106 106
107 107 def set_dirstate_item(
108 108 &self,
109 109 path: PyObject,
110 110 item: DirstateItem
111 111 ) -> PyResult<PyObject> {
112 112 let f = path.extract::<PyBytes>(py)?;
113 113 let filename = HgPath::new(f.data(py));
114 114 self.inner(py)
115 115 .borrow_mut()
116 116 .set_entry(filename, item.get_entry(py))
117 117 .map_err(|e| v2_error(py, e))?;
118 118 Ok(py.None())
119 119 }
120 120
121 121 def set_tracked(&self, f: PyObject) -> PyResult<PyBool> {
122 122 let bytes = f.extract::<PyBytes>(py)?;
123 123 let path = HgPath::new(bytes.data(py));
124 124 let res = self.inner(py).borrow_mut().set_tracked(path);
125 125 let was_tracked = res.or_else(|_| {
126 126 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
127 127 })?;
128 128 Ok(was_tracked.to_py_object(py))
129 129 }
130 130
131 131 def set_untracked(&self, f: PyObject) -> PyResult<PyBool> {
132 132 let bytes = f.extract::<PyBytes>(py)?;
133 133 let path = HgPath::new(bytes.data(py));
134 134 let res = self.inner(py).borrow_mut().set_untracked(path);
135 135 let was_tracked = res.or_else(|_| {
136 136 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
137 137 })?;
138 138 Ok(was_tracked.to_py_object(py))
139 139 }
140 140
141 141 def set_clean(
142 142 &self,
143 143 f: PyObject,
144 144 mode: u32,
145 145 size: u32,
146 146 mtime: (i64, u32, bool)
147 147 ) -> PyResult<PyNone> {
148 148 let (mtime_s, mtime_ns, second_ambiguous) = mtime;
149 149 let timestamp = TruncatedTimestamp::new_truncate(
150 150 mtime_s, mtime_ns, second_ambiguous
151 151 );
152 152 let bytes = f.extract::<PyBytes>(py)?;
153 153 let path = HgPath::new(bytes.data(py));
154 154 let res = self.inner(py).borrow_mut().set_clean(
155 155 path, mode, size, timestamp,
156 156 );
157 157 res.or_else(|_| {
158 158 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
159 159 })?;
160 160 Ok(PyNone)
161 161 }
162 162
163 163 def set_possibly_dirty(&self, f: PyObject) -> PyResult<PyNone> {
164 164 let bytes = f.extract::<PyBytes>(py)?;
165 165 let path = HgPath::new(bytes.data(py));
166 166 let res = self.inner(py).borrow_mut().set_possibly_dirty(path);
167 167 res.or_else(|_| {
168 168 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
169 169 })?;
170 170 Ok(PyNone)
171 171 }
172 172
173 173 def reset_state(
174 174 &self,
175 175 f: PyObject,
176 176 wc_tracked: bool,
177 177 p1_tracked: bool,
178 178 p2_info: bool,
179 179 has_meaningful_mtime: bool,
180 180 parentfiledata: Option<(u32, u32, Option<(i64, u32, bool)>)>,
181 181 ) -> PyResult<PyNone> {
182 182 let mut has_meaningful_mtime = has_meaningful_mtime;
183 183 let parent_file_data = match parentfiledata {
184 184 None => {
185 185 has_meaningful_mtime = false;
186 186 None
187 187 },
188 188 Some(data) => {
189 189 let (mode, size, mtime_info) = data;
190 190 let mtime = if let Some(mtime_info) = mtime_info {
191 191 let (mtime_s, mtime_ns, second_ambiguous) = mtime_info;
192 192 let timestamp = TruncatedTimestamp::new_truncate(
193 193 mtime_s, mtime_ns, second_ambiguous
194 194 );
195 195 Some(timestamp)
196 196 } else {
197 197 has_meaningful_mtime = false;
198 198 None
199 199 };
200 200 Some(ParentFileData {
201 201 mode_size: Some((mode, size)),
202 202 mtime,
203 203 })
204 204 }
205 205 };
206 206 let bytes = f.extract::<PyBytes>(py)?;
207 207 let path = HgPath::new(bytes.data(py));
208 208 let res = self.inner(py).borrow_mut().reset_state(
209 209 path,
210 210 wc_tracked,
211 211 p1_tracked,
212 212 p2_info,
213 213 has_meaningful_mtime,
214 214 parent_file_data,
215 215 );
216 216 res.or_else(|_| {
217 217 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
218 218 })?;
219 219 Ok(PyNone)
220 220 }
221 221
222 222 def drop_item_and_copy_source(
223 223 &self,
224 224 f: PyBytes,
225 225 ) -> PyResult<PyNone> {
226 226 self.inner(py)
227 227 .borrow_mut()
228 228 .drop_entry_and_copy_source(HgPath::new(f.data(py)))
229 229 .map_err(|e |dirstate_error(py, e))?;
230 230 Ok(PyNone)
231 231 }
232 232
233 233 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
234 234 let d = d.extract::<PyBytes>(py)?;
235 235 Ok(self.inner(py).borrow_mut()
236 236 .has_tracked_dir(HgPath::new(d.data(py)))
237 237 .map_err(|e| {
238 238 PyErr::new::<exc::ValueError, _>(py, e.to_string())
239 239 })?
240 240 .to_py_object(py))
241 241 }
242 242
243 243 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
244 244 let d = d.extract::<PyBytes>(py)?;
245 245 Ok(self.inner(py).borrow_mut()
246 246 .has_dir(HgPath::new(d.data(py)))
247 247 .map_err(|e| {
248 248 PyErr::new::<exc::ValueError, _>(py, e.to_string())
249 249 })?
250 250 .to_py_object(py))
251 251 }
252 252
253 253 def write_v1(
254 254 &self,
255 255 p1: PyObject,
256 256 p2: PyObject,
257 257 ) -> PyResult<PyBytes> {
258 258 let inner = self.inner(py).borrow();
259 259 let parents = DirstateParents {
260 260 p1: extract_node_id(py, &p1)?,
261 261 p2: extract_node_id(py, &p2)?,
262 262 };
263 263 let result = inner.pack_v1(parents);
264 264 match result {
265 265 Ok(packed) => Ok(PyBytes::new(py, &packed)),
266 266 Err(_) => Err(PyErr::new::<exc::OSError, _>(
267 267 py,
268 268 "Dirstate error".to_string(),
269 269 )),
270 270 }
271 271 }
272 272
273 273 /// Returns new data together with whether that data should be appended to
274 274 /// the existing data file whose content is at `self.on_disk` (True),
275 275 /// instead of written to a new data file (False).
276 276 def write_v2(
277 277 &self,
278 278 can_append: bool,
279 279 ) -> PyResult<PyObject> {
280 280 let inner = self.inner(py).borrow();
281 281 let result = inner.pack_v2(can_append);
282 282 match result {
283 283 Ok((packed, tree_metadata, append)) => {
284 284 let packed = PyBytes::new(py, &packed);
285 285 let tree_metadata = PyBytes::new(py, tree_metadata.as_bytes());
286 286 let tuple = (packed, tree_metadata, append);
287 287 Ok(tuple.to_py_object(py).into_object())
288 288 },
289 289 Err(_) => Err(PyErr::new::<exc::OSError, _>(
290 290 py,
291 291 "Dirstate error".to_string(),
292 292 )),
293 293 }
294 294 }
295 295
296 296 def filefoldmapasdict(&self) -> PyResult<PyDict> {
297 297 let dict = PyDict::new(py);
298 298 for item in self.inner(py).borrow_mut().iter() {
299 299 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
300 300 if entry.state() != EntryState::Removed {
301 301 let key = normalize_case(path);
302 302 let value = path;
303 303 dict.set_item(
304 304 py,
305 305 PyBytes::new(py, key.as_bytes()).into_object(),
306 306 PyBytes::new(py, value.as_bytes()).into_object(),
307 307 )?;
308 308 }
309 309 }
310 310 Ok(dict)
311 311 }
312 312
313 313 def __len__(&self) -> PyResult<usize> {
314 314 Ok(self.inner(py).borrow().len())
315 315 }
316 316
317 317 def __contains__(&self, key: PyObject) -> PyResult<bool> {
318 318 let key = key.extract::<PyBytes>(py)?;
319 319 self.inner(py)
320 320 .borrow()
321 321 .contains_key(HgPath::new(key.data(py)))
322 322 .map_err(|e| v2_error(py, e))
323 323 }
324 324
325 325 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
326 326 let key = key.extract::<PyBytes>(py)?;
327 327 let key = HgPath::new(key.data(py));
328 328 match self
329 329 .inner(py)
330 330 .borrow()
331 331 .get(key)
332 332 .map_err(|e| v2_error(py, e))?
333 333 {
334 334 Some(entry) => {
335 335 Ok(DirstateItem::new_as_pyobject(py, entry)?)
336 336 },
337 337 None => Err(PyErr::new::<exc::KeyError, _>(
338 338 py,
339 339 String::from_utf8_lossy(key.as_bytes()),
340 340 )),
341 341 }
342 342 }
343 343
344 344 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
345 345 let leaked_ref = self.inner(py).leak_immutable();
346 346 DirstateMapKeysIterator::from_inner(
347 347 py,
348 348 unsafe { leaked_ref.map(py, |o| o.iter()) },
349 349 )
350 350 }
351 351
352 352 def items(&self) -> PyResult<DirstateMapItemsIterator> {
353 353 let leaked_ref = self.inner(py).leak_immutable();
354 354 DirstateMapItemsIterator::from_inner(
355 355 py,
356 356 unsafe { leaked_ref.map(py, |o| o.iter()) },
357 357 )
358 358 }
359 359
360 360 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
361 361 let leaked_ref = self.inner(py).leak_immutable();
362 362 DirstateMapKeysIterator::from_inner(
363 363 py,
364 364 unsafe { leaked_ref.map(py, |o| o.iter()) },
365 365 )
366 366 }
367 367
368 368 // TODO all copymap* methods, see docstring above
369 369 def copymapcopy(&self) -> PyResult<PyDict> {
370 370 let dict = PyDict::new(py);
371 371 for item in self.inner(py).borrow().copy_map_iter() {
372 372 let (key, value) = item.map_err(|e| v2_error(py, e))?;
373 373 dict.set_item(
374 374 py,
375 375 PyBytes::new(py, key.as_bytes()),
376 376 PyBytes::new(py, value.as_bytes()),
377 377 )?;
378 378 }
379 379 Ok(dict)
380 380 }
381 381
382 382 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
383 383 let key = key.extract::<PyBytes>(py)?;
384 384 match self
385 385 .inner(py)
386 386 .borrow()
387 387 .copy_map_get(HgPath::new(key.data(py)))
388 388 .map_err(|e| v2_error(py, e))?
389 389 {
390 390 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
391 391 None => Err(PyErr::new::<exc::KeyError, _>(
392 392 py,
393 393 String::from_utf8_lossy(key.data(py)),
394 394 )),
395 395 }
396 396 }
397 397 def copymap(&self) -> PyResult<CopyMap> {
398 398 CopyMap::from_inner(py, self.clone_ref(py))
399 399 }
400 400
401 401 def copymaplen(&self) -> PyResult<usize> {
402 402 Ok(self.inner(py).borrow().copy_map_len())
403 403 }
404 404 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
405 405 let key = key.extract::<PyBytes>(py)?;
406 406 self.inner(py)
407 407 .borrow()
408 408 .copy_map_contains_key(HgPath::new(key.data(py)))
409 409 .map_err(|e| v2_error(py, e))
410 410 }
411 411 def copymapget(
412 412 &self,
413 413 key: PyObject,
414 414 default: Option<PyObject>
415 415 ) -> PyResult<Option<PyObject>> {
416 416 let key = key.extract::<PyBytes>(py)?;
417 417 match self
418 418 .inner(py)
419 419 .borrow()
420 420 .copy_map_get(HgPath::new(key.data(py)))
421 421 .map_err(|e| v2_error(py, e))?
422 422 {
423 423 Some(copy) => Ok(Some(
424 424 PyBytes::new(py, copy.as_bytes()).into_object(),
425 425 )),
426 426 None => Ok(default),
427 427 }
428 428 }
429 429 def copymapsetitem(
430 430 &self,
431 431 key: PyObject,
432 432 value: PyObject
433 433 ) -> PyResult<PyObject> {
434 434 let key = key.extract::<PyBytes>(py)?;
435 435 let value = value.extract::<PyBytes>(py)?;
436 436 self.inner(py)
437 437 .borrow_mut()
438 438 .copy_map_insert(
439 439 HgPathBuf::from_bytes(key.data(py)),
440 440 HgPathBuf::from_bytes(value.data(py)),
441 441 )
442 442 .map_err(|e| v2_error(py, e))?;
443 443 Ok(py.None())
444 444 }
445 445 def copymappop(
446 446 &self,
447 447 key: PyObject,
448 448 default: Option<PyObject>
449 449 ) -> PyResult<Option<PyObject>> {
450 450 let key = key.extract::<PyBytes>(py)?;
451 451 match self
452 452 .inner(py)
453 453 .borrow_mut()
454 454 .copy_map_remove(HgPath::new(key.data(py)))
455 455 .map_err(|e| v2_error(py, e))?
456 456 {
457 457 Some(copy) => Ok(Some(
458 458 PyBytes::new(py, copy.as_bytes()).into_object(),
459 459 )),
460 460 None => Ok(default),
461 461 }
462 462 }
463 463
464 464 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
465 465 let leaked_ref = self.inner(py).leak_immutable();
466 466 CopyMapKeysIterator::from_inner(
467 467 py,
468 468 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
469 469 )
470 470 }
471 471
472 472 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
473 473 let leaked_ref = self.inner(py).leak_immutable();
474 474 CopyMapItemsIterator::from_inner(
475 475 py,
476 476 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
477 477 )
478 478 }
479 479
480 480 def tracked_dirs(&self) -> PyResult<PyList> {
481 481 let dirs = PyList::new(py, &[]);
482 482 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
483 483 .map_err(|e |dirstate_error(py, e))?
484 484 {
485 485 let path = path.map_err(|e| v2_error(py, e))?;
486 486 let path = PyBytes::new(py, path.as_bytes());
487 487 dirs.append(py, path.into_object())
488 488 }
489 489 Ok(dirs)
490 490 }
491 491
492 def setparents_fixup(&self) -> PyResult<PyDict> {
493 let dict = PyDict::new(py);
494 let copies = self.inner(py).borrow_mut().setparents_fixup();
495 for (key, value) in copies.map_err(|e| v2_error(py, e))? {
496 dict.set_item(
497 py,
498 PyBytes::new(py, key.as_bytes()),
499 PyBytes::new(py, value.as_bytes()),
500 )?;
501 }
502 Ok(dict)
503 }
504
492 505 def debug_iter(&self, all: bool) -> PyResult<PyList> {
493 506 let dirs = PyList::new(py, &[]);
494 507 for item in self.inner(py).borrow().debug_iter(all) {
495 508 let (path, (state, mode, size, mtime)) =
496 509 item.map_err(|e| v2_error(py, e))?;
497 510 let path = PyBytes::new(py, path.as_bytes());
498 511 let item = (path, state, mode, size, mtime);
499 512 dirs.append(py, item.to_py_object(py).into_object())
500 513 }
501 514 Ok(dirs)
502 515 }
503 516 });
504 517
505 518 impl DirstateMap {
506 519 pub fn get_inner_mut<'a>(
507 520 &'a self,
508 521 py: Python<'a>,
509 522 ) -> RefMut<'a, OwningDirstateMap> {
510 523 self.inner(py).borrow_mut()
511 524 }
512 525 fn translate_key(
513 526 py: Python,
514 527 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
515 528 ) -> PyResult<Option<PyBytes>> {
516 529 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
517 530 Ok(Some(PyBytes::new(py, f.as_bytes())))
518 531 }
519 532 fn translate_key_value(
520 533 py: Python,
521 534 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
522 535 ) -> PyResult<Option<(PyBytes, PyObject)>> {
523 536 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
524 537 Ok(Some((
525 538 PyBytes::new(py, f.as_bytes()),
526 539 DirstateItem::new_as_pyobject(py, entry)?,
527 540 )))
528 541 }
529 542 }
530 543
531 544 py_shared_iterator!(
532 545 DirstateMapKeysIterator,
533 546 UnsafePyLeaked<StateMapIter<'static>>,
534 547 DirstateMap::translate_key,
535 548 Option<PyBytes>
536 549 );
537 550
538 551 py_shared_iterator!(
539 552 DirstateMapItemsIterator,
540 553 UnsafePyLeaked<StateMapIter<'static>>,
541 554 DirstateMap::translate_key_value,
542 555 Option<(PyBytes, PyObject)>
543 556 );
544 557
545 558 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
546 559 let bytes = obj.extract::<PyBytes>(py)?;
547 560 match bytes.data(py).try_into() {
548 561 Ok(s) => Ok(s),
549 562 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
550 563 }
551 564 }
552 565
553 566 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
554 567 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
555 568 }
556 569
557 570 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
558 571 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
559 572 }
General Comments 0
You need to be logged in to leave comments. Login now