##// END OF EJS Templates
rust-dirstate: remember the data file uuid dirstate was loaded with...
Raphaël Gomès -
r51138:6cce0afc stable
parent child Browse files
Show More
@@ -1,733 +1,737 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 from .i18n import _
8 8
9 9 from . import (
10 10 error,
11 11 pathutil,
12 12 policy,
13 13 testing,
14 14 txnutil,
15 15 util,
16 16 )
17 17
18 18 from .dirstateutils import (
19 19 docket as docketmod,
20 20 v2,
21 21 )
22 22
23 23 parsers = policy.importmod('parsers')
24 24 rustmod = policy.importrust('dirstate')
25 25
26 26 propertycache = util.propertycache
27 27
28 28 if rustmod is None:
29 29 DirstateItem = parsers.DirstateItem
30 30 else:
31 31 DirstateItem = rustmod.DirstateItem
32 32
33 33 rangemask = 0x7FFFFFFF
34 34
35 35 WRITE_MODE_AUTO = 0
36 36 WRITE_MODE_FORCE_NEW = 1
37 37 WRITE_MODE_FORCE_APPEND = 2
38 38
39 39
40 40 V2_MAX_READ_ATTEMPTS = 5
41 41
42 42
43 43 class _dirstatemapcommon:
44 44 """
45 45 Methods that are identical for both implementations of the dirstatemap
46 46 class, with and without Rust extensions enabled.
47 47 """
48 48
49 49 # please pytype
50 50
51 51 _map = None
52 52 copymap = None
53 53
54 54 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
55 55 self._use_dirstate_v2 = use_dirstate_v2
56 56 self._nodeconstants = nodeconstants
57 57 self._ui = ui
58 58 self._opener = opener
59 59 self._root = root
60 60 self._filename = b'dirstate'
61 61 self._nodelen = 20 # Also update Rust code when changing this!
62 62 self._parents = None
63 63 self._dirtyparents = False
64 64 self._docket = None
65 65 write_mode = ui.config(b"devel", b"dirstate.v2.data_update_mode")
66 66 if write_mode == b"auto":
67 67 self._write_mode = WRITE_MODE_AUTO
68 68 elif write_mode == b"force-append":
69 69 self._write_mode = WRITE_MODE_FORCE_APPEND
70 70 elif write_mode == b"force-new":
71 71 self._write_mode = WRITE_MODE_FORCE_NEW
72 72 else:
73 73 # unknown value, fallback to default
74 74 self._write_mode = WRITE_MODE_AUTO
75 75
76 76 # for consistent view between _pl() and _read() invocations
77 77 self._pendingmode = None
78 78
79 79 def _set_identity(self):
80 80 # ignore HG_PENDING because identity is used only for writing
81 81 file_path = self._opener.join(self._filename)
82 82 self.identity = util.filestat.frompath(file_path)
83 83
84 84 def preload(self):
85 85 """Loads the underlying data, if it's not already loaded"""
86 86 self._map
87 87
88 88 def get(self, key, default=None):
89 89 return self._map.get(key, default)
90 90
91 91 def __len__(self):
92 92 return len(self._map)
93 93
94 94 def __iter__(self):
95 95 return iter(self._map)
96 96
97 97 def __contains__(self, key):
98 98 return key in self._map
99 99
100 100 def __getitem__(self, item):
101 101 return self._map[item]
102 102
103 103 ### disk interaction
104 104
105 105 def _opendirstatefile(self):
106 106 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
107 107 if self._pendingmode is not None and self._pendingmode != mode:
108 108 fp.close()
109 109 raise error.Abort(
110 110 _(b'working directory state may be changed parallelly')
111 111 )
112 112 self._pendingmode = mode
113 113 return fp
114 114
115 115 def _readdirstatefile(self, size=-1):
116 116 try:
117 117 with self._opendirstatefile() as fp:
118 118 return fp.read(size)
119 119 except FileNotFoundError:
120 120 # File doesn't exist, so the current state is empty
121 121 return b''
122 122
123 123 @property
124 124 def docket(self):
125 125 if not self._docket:
126 126 if not self._use_dirstate_v2:
127 127 raise error.ProgrammingError(
128 128 b'dirstate only has a docket in v2 format'
129 129 )
130 130 self._set_identity()
131 131 self._docket = docketmod.DirstateDocket.parse(
132 132 self._readdirstatefile(), self._nodeconstants
133 133 )
134 134 return self._docket
135 135
136 136 def _read_v2_data(self):
137 137 data = None
138 138 attempts = 0
139 139 while attempts < V2_MAX_READ_ATTEMPTS:
140 140 attempts += 1
141 141 try:
142 142 # TODO: use mmap when possible
143 143 data = self._opener.read(self.docket.data_filename())
144 144 except FileNotFoundError:
145 145 # read race detected between docket and data file
146 146 # reload the docket and retry
147 147 self._docket = None
148 148 if data is None:
149 149 assert attempts >= V2_MAX_READ_ATTEMPTS
150 150 msg = b"dirstate read race happened %d times in a row"
151 151 msg %= attempts
152 152 raise error.Abort(msg)
153 153 return self._opener.read(self.docket.data_filename())
154 154
155 155 def write_v2_no_append(self, tr, st, meta, packed):
156 156 old_docket = self.docket
157 157 new_docket = docketmod.DirstateDocket.with_new_uuid(
158 158 self.parents(), len(packed), meta
159 159 )
160 160 if old_docket.uuid == new_docket.uuid:
161 161 raise error.ProgrammingError(b'dirstate docket name collision')
162 162 data_filename = new_docket.data_filename()
163 163 self._opener.write(data_filename, packed)
164 164 # Write the new docket after the new data file has been
165 165 # written. Because `st` was opened with `atomictemp=True`,
166 166 # the actual `.hg/dirstate` file is only affected on close.
167 167 st.write(new_docket.serialize())
168 168 st.close()
169 169 # Remove the old data file after the new docket pointing to
170 170 # the new data file was written.
171 171 if old_docket.uuid:
172 172 data_filename = old_docket.data_filename()
173 173 unlink = lambda _tr=None: self._opener.unlink(data_filename)
174 174 if tr:
175 175 category = b"dirstate-v2-clean-" + old_docket.uuid
176 176 tr.addpostclose(category, unlink)
177 177 else:
178 178 unlink()
179 179 self._docket = new_docket
180 180
181 181 ### reading/setting parents
182 182
183 183 def parents(self):
184 184 if not self._parents:
185 185 if self._use_dirstate_v2:
186 186 self._parents = self.docket.parents
187 187 else:
188 188 read_len = self._nodelen * 2
189 189 st = self._readdirstatefile(read_len)
190 190 l = len(st)
191 191 if l == read_len:
192 192 self._parents = (
193 193 st[: self._nodelen],
194 194 st[self._nodelen : 2 * self._nodelen],
195 195 )
196 196 elif l == 0:
197 197 self._parents = (
198 198 self._nodeconstants.nullid,
199 199 self._nodeconstants.nullid,
200 200 )
201 201 else:
202 202 raise error.Abort(
203 203 _(b'working directory state appears damaged!')
204 204 )
205 205
206 206 return self._parents
207 207
208 208
209 209 class dirstatemap(_dirstatemapcommon):
210 210 """Map encapsulating the dirstate's contents.
211 211
212 212 The dirstate contains the following state:
213 213
214 214 - `identity` is the identity of the dirstate file, which can be used to
215 215 detect when changes have occurred to the dirstate file.
216 216
217 217 - `parents` is a pair containing the parents of the working copy. The
218 218 parents are updated by calling `setparents`.
219 219
220 220 - the state map maps filenames to tuples of (state, mode, size, mtime),
221 221 where state is a single character representing 'normal', 'added',
222 222 'removed', or 'merged'. It is read by treating the dirstate as a
223 223 dict. File state is updated by calling various methods (see each
224 224 documentation for details):
225 225
226 226 - `reset_state`,
227 227 - `set_tracked`
228 228 - `set_untracked`
229 229 - `set_clean`
230 230 - `set_possibly_dirty`
231 231
232 232 - `copymap` maps destination filenames to their source filename.
233 233
234 234 The dirstate also provides the following views onto the state:
235 235
236 236 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
237 237 form that they appear as in the dirstate.
238 238
239 239 - `dirfoldmap` is a dict mapping normalized directory names to the
240 240 denormalized form that they appear as in the dirstate.
241 241 """
242 242
243 243 ### Core data storage and access
244 244
245 245 @propertycache
246 246 def _map(self):
247 247 self._map = {}
248 248 self.read()
249 249 return self._map
250 250
251 251 @propertycache
252 252 def copymap(self):
253 253 self.copymap = {}
254 254 self._map
255 255 return self.copymap
256 256
257 257 def clear(self):
258 258 self._map.clear()
259 259 self.copymap.clear()
260 260 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
261 261 util.clearcachedproperty(self, b"_dirs")
262 262 util.clearcachedproperty(self, b"_alldirs")
263 263 util.clearcachedproperty(self, b"filefoldmap")
264 264 util.clearcachedproperty(self, b"dirfoldmap")
265 265
266 266 def items(self):
267 267 return self._map.items()
268 268
269 269 # forward for python2,3 compat
270 270 iteritems = items
271 271
272 272 def debug_iter(self, all):
273 273 """
274 274 Return an iterator of (filename, state, mode, size, mtime) tuples
275 275
276 276 `all` is unused when Rust is not enabled
277 277 """
278 278 for (filename, item) in self.items():
279 279 yield (filename, item.state, item.mode, item.size, item.mtime)
280 280
281 281 def keys(self):
282 282 return self._map.keys()
283 283
284 284 ### reading/setting parents
285 285
286 286 def setparents(self, p1, p2, fold_p2=False):
287 287 self._parents = (p1, p2)
288 288 self._dirtyparents = True
289 289 copies = {}
290 290 if fold_p2:
291 291 for f, s in self._map.items():
292 292 # Discard "merged" markers when moving away from a merge state
293 293 if s.p2_info:
294 294 source = self.copymap.pop(f, None)
295 295 if source:
296 296 copies[f] = source
297 297 s.drop_merge_data()
298 298 return copies
299 299
300 300 ### disk interaction
301 301
302 302 def read(self):
303 303 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
304 304 if self._use_dirstate_v2:
305 305
306 306 if not self.docket.uuid:
307 307 return
308 308 testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
309 309 st = self._read_v2_data()
310 310 else:
311 311 self._set_identity()
312 312 st = self._readdirstatefile()
313 313
314 314 if not st:
315 315 return
316 316
317 317 # TODO: adjust this estimate for dirstate-v2
318 318 if util.safehasattr(parsers, b'dict_new_presized'):
319 319 # Make an estimate of the number of files in the dirstate based on
320 320 # its size. This trades wasting some memory for avoiding costly
321 321 # resizes. Each entry have a prefix of 17 bytes followed by one or
322 322 # two path names. Studies on various large-scale real-world repositories
323 323 # found 54 bytes a reasonable upper limit for the average path names.
324 324 # Copy entries are ignored for the sake of this estimate.
325 325 self._map = parsers.dict_new_presized(len(st) // 71)
326 326
327 327 # Python's garbage collector triggers a GC each time a certain number
328 328 # of container objects (the number being defined by
329 329 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
330 330 # for each file in the dirstate. The C version then immediately marks
331 331 # them as not to be tracked by the collector. However, this has no
332 332 # effect on when GCs are triggered, only on what objects the GC looks
333 333 # into. This means that O(number of files) GCs are unavoidable.
334 334 # Depending on when in the process's lifetime the dirstate is parsed,
335 335 # this can get very expensive. As a workaround, disable GC while
336 336 # parsing the dirstate.
337 337 #
338 338 # (we cannot decorate the function directly since it is in a C module)
339 339 if self._use_dirstate_v2:
340 340 p = self.docket.parents
341 341 meta = self.docket.tree_metadata
342 342 parse_dirstate = util.nogc(v2.parse_dirstate)
343 343 parse_dirstate(self._map, self.copymap, st, meta)
344 344 else:
345 345 parse_dirstate = util.nogc(parsers.parse_dirstate)
346 346 p = parse_dirstate(self._map, self.copymap, st)
347 347 if not self._dirtyparents:
348 348 self.setparents(*p)
349 349
350 350 # Avoid excess attribute lookups by fast pathing certain checks
351 351 self.__contains__ = self._map.__contains__
352 352 self.__getitem__ = self._map.__getitem__
353 353 self.get = self._map.get
354 354
355 355 def write(self, tr, st):
356 356 if self._use_dirstate_v2:
357 357 packed, meta = v2.pack_dirstate(self._map, self.copymap)
358 358 self.write_v2_no_append(tr, st, meta, packed)
359 359 else:
360 360 packed = parsers.pack_dirstate(
361 361 self._map, self.copymap, self.parents()
362 362 )
363 363 st.write(packed)
364 364 st.close()
365 365 self._dirtyparents = False
366 366
367 367 @propertycache
368 368 def identity(self):
369 369 self._map
370 370 return self.identity
371 371
372 372 ### code related to maintaining and accessing "extra" property
373 373 # (e.g. "has_dir")
374 374
375 375 def _dirs_incr(self, filename, old_entry=None):
376 376 """increment the dirstate counter if applicable"""
377 377 if (
378 378 old_entry is None or old_entry.removed
379 379 ) and "_dirs" in self.__dict__:
380 380 self._dirs.addpath(filename)
381 381 if old_entry is None and "_alldirs" in self.__dict__:
382 382 self._alldirs.addpath(filename)
383 383
384 384 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
385 385 """decrement the dirstate counter if applicable"""
386 386 if old_entry is not None:
387 387 if "_dirs" in self.__dict__ and not old_entry.removed:
388 388 self._dirs.delpath(filename)
389 389 if "_alldirs" in self.__dict__ and not remove_variant:
390 390 self._alldirs.delpath(filename)
391 391 elif remove_variant and "_alldirs" in self.__dict__:
392 392 self._alldirs.addpath(filename)
393 393 if "filefoldmap" in self.__dict__:
394 394 normed = util.normcase(filename)
395 395 self.filefoldmap.pop(normed, None)
396 396
397 397 @propertycache
398 398 def filefoldmap(self):
399 399 """Returns a dictionary mapping normalized case paths to their
400 400 non-normalized versions.
401 401 """
402 402 try:
403 403 makefilefoldmap = parsers.make_file_foldmap
404 404 except AttributeError:
405 405 pass
406 406 else:
407 407 return makefilefoldmap(
408 408 self._map, util.normcasespec, util.normcasefallback
409 409 )
410 410
411 411 f = {}
412 412 normcase = util.normcase
413 413 for name, s in self._map.items():
414 414 if not s.removed:
415 415 f[normcase(name)] = name
416 416 f[b'.'] = b'.' # prevents useless util.fspath() invocation
417 417 return f
418 418
419 419 @propertycache
420 420 def dirfoldmap(self):
421 421 f = {}
422 422 normcase = util.normcase
423 423 for name in self._dirs:
424 424 f[normcase(name)] = name
425 425 return f
426 426
427 427 def hastrackeddir(self, d):
428 428 """
429 429 Returns True if the dirstate contains a tracked (not removed) file
430 430 in this directory.
431 431 """
432 432 return d in self._dirs
433 433
434 434 def hasdir(self, d):
435 435 """
436 436 Returns True if the dirstate contains a file (tracked or removed)
437 437 in this directory.
438 438 """
439 439 return d in self._alldirs
440 440
441 441 @propertycache
442 442 def _dirs(self):
443 443 return pathutil.dirs(self._map, only_tracked=True)
444 444
445 445 @propertycache
446 446 def _alldirs(self):
447 447 return pathutil.dirs(self._map)
448 448
449 449 ### code related to manipulation of entries and copy-sources
450 450
451 451 def reset_state(
452 452 self,
453 453 filename,
454 454 wc_tracked=False,
455 455 p1_tracked=False,
456 456 p2_info=False,
457 457 has_meaningful_mtime=True,
458 458 parentfiledata=None,
459 459 ):
460 460 """Set a entry to a given state, diregarding all previous state
461 461
462 462 This is to be used by the part of the dirstate API dedicated to
463 463 adjusting the dirstate after a update/merge.
464 464
465 465 note: calling this might result to no entry existing at all if the
466 466 dirstate map does not see any point at having one for this file
467 467 anymore.
468 468 """
469 469 # copy information are now outdated
470 470 # (maybe new information should be in directly passed to this function)
471 471 self.copymap.pop(filename, None)
472 472
473 473 if not (p1_tracked or p2_info or wc_tracked):
474 474 old_entry = self._map.get(filename)
475 475 self._drop_entry(filename)
476 476 self._dirs_decr(filename, old_entry=old_entry)
477 477 return
478 478
479 479 old_entry = self._map.get(filename)
480 480 self._dirs_incr(filename, old_entry)
481 481 entry = DirstateItem(
482 482 wc_tracked=wc_tracked,
483 483 p1_tracked=p1_tracked,
484 484 p2_info=p2_info,
485 485 has_meaningful_mtime=has_meaningful_mtime,
486 486 parentfiledata=parentfiledata,
487 487 )
488 488 self._map[filename] = entry
489 489
490 490 def set_tracked(self, filename):
491 491 new = False
492 492 entry = self.get(filename)
493 493 if entry is None:
494 494 self._dirs_incr(filename)
495 495 entry = DirstateItem(
496 496 wc_tracked=True,
497 497 )
498 498
499 499 self._map[filename] = entry
500 500 new = True
501 501 elif not entry.tracked:
502 502 self._dirs_incr(filename, entry)
503 503 entry.set_tracked()
504 504 self._refresh_entry(filename, entry)
505 505 new = True
506 506 else:
507 507 # XXX This is probably overkill for more case, but we need this to
508 508 # fully replace the `normallookup` call with `set_tracked` one.
509 509 # Consider smoothing this in the future.
510 510 entry.set_possibly_dirty()
511 511 self._refresh_entry(filename, entry)
512 512 return new
513 513
514 514 def set_untracked(self, f):
515 515 """Mark a file as no longer tracked in the dirstate map"""
516 516 entry = self.get(f)
517 517 if entry is None:
518 518 return False
519 519 else:
520 520 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
521 521 if not entry.p2_info:
522 522 self.copymap.pop(f, None)
523 523 entry.set_untracked()
524 524 self._refresh_entry(f, entry)
525 525 return True
526 526
527 527 def set_clean(self, filename, mode, size, mtime):
528 528 """mark a file as back to a clean state"""
529 529 entry = self[filename]
530 530 size = size & rangemask
531 531 entry.set_clean(mode, size, mtime)
532 532 self._refresh_entry(filename, entry)
533 533 self.copymap.pop(filename, None)
534 534
535 535 def set_possibly_dirty(self, filename):
536 536 """record that the current state of the file on disk is unknown"""
537 537 entry = self[filename]
538 538 entry.set_possibly_dirty()
539 539 self._refresh_entry(filename, entry)
540 540
541 541 def _refresh_entry(self, f, entry):
542 542 """record updated state of an entry"""
543 543 if not entry.any_tracked:
544 544 self._map.pop(f, None)
545 545
546 546 def _drop_entry(self, f):
547 547 """remove any entry for file f
548 548
549 549 This should also drop associated copy information
550 550
551 551 The fact we actually need to drop it is the responsability of the caller"""
552 552 self._map.pop(f, None)
553 553 self.copymap.pop(f, None)
554 554
555 555
556 556 if rustmod is not None:
557 557
558 558 class dirstatemap(_dirstatemapcommon):
559 559
560 560 ### Core data storage and access
561 561
562 562 @propertycache
563 563 def _map(self):
564 564 """
565 565 Fills the Dirstatemap when called.
566 566 """
567 567 # ignore HG_PENDING because identity is used only for writing
568 568 self._set_identity()
569 569
570 570 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
571 571 if self._use_dirstate_v2:
572 572 self.docket # load the data if needed
573 573 testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
574 574 if not self.docket.uuid:
575 575 data = b''
576 self._map = rustmod.DirstateMap.new_empty()
576 577 else:
577 578 data = self._read_v2_data()
578 self._map = rustmod.DirstateMap.new_v2(
579 data, self.docket.data_size, self.docket.tree_metadata
580 )
579 self._map = rustmod.DirstateMap.new_v2(
580 data,
581 self.docket.data_size,
582 self.docket.tree_metadata,
583 self.docket.uuid,
584 )
581 585 parents = self.docket.parents
582 586 else:
583 587 self._set_identity()
584 588 self._map, parents = rustmod.DirstateMap.new_v1(
585 589 self._readdirstatefile()
586 590 )
587 591
588 592 if parents and not self._dirtyparents:
589 593 self.setparents(*parents)
590 594
591 595 self.__contains__ = self._map.__contains__
592 596 self.__getitem__ = self._map.__getitem__
593 597 self.get = self._map.get
594 598 return self._map
595 599
596 600 @property
597 601 def copymap(self):
598 602 return self._map.copymap()
599 603
600 604 def debug_iter(self, all):
601 605 """
602 606 Return an iterator of (filename, state, mode, size, mtime) tuples
603 607
604 608 `all`: also include with `state == b' '` dirstate tree nodes that
605 609 don't have an associated `DirstateItem`.
606 610
607 611 """
608 612 return self._map.debug_iter(all)
609 613
610 614 def clear(self):
611 615 self._map.clear()
612 616 self.setparents(
613 617 self._nodeconstants.nullid, self._nodeconstants.nullid
614 618 )
615 619 util.clearcachedproperty(self, b"_dirs")
616 620 util.clearcachedproperty(self, b"_alldirs")
617 621 util.clearcachedproperty(self, b"dirfoldmap")
618 622
619 623 def items(self):
620 624 return self._map.items()
621 625
622 626 # forward for python2,3 compat
623 627 iteritems = items
624 628
625 629 def keys(self):
626 630 return iter(self._map)
627 631
628 632 ### reading/setting parents
629 633
630 634 def setparents(self, p1, p2, fold_p2=False):
631 635 self._parents = (p1, p2)
632 636 self._dirtyparents = True
633 637 copies = {}
634 638 if fold_p2:
635 639 copies = self._map.setparents_fixup()
636 640 return copies
637 641
638 642 ### disk interaction
639 643
640 644 @propertycache
641 645 def identity(self):
642 646 self._map
643 647 return self.identity
644 648
645 649 def write(self, tr, st):
646 650 if not self._use_dirstate_v2:
647 651 p1, p2 = self.parents()
648 652 packed = self._map.write_v1(p1, p2)
649 653 st.write(packed)
650 654 st.close()
651 655 self._dirtyparents = False
652 656 return
653 657
654 658 # We can only append to an existing data file if there is one
655 659 write_mode = self._write_mode
656 660 if self.docket.uuid is None:
657 661 write_mode = WRITE_MODE_FORCE_NEW
658 662 packed, meta, append = self._map.write_v2(write_mode)
659 663 if append:
660 664 docket = self.docket
661 665 data_filename = docket.data_filename()
662 666 with self._opener(data_filename, b'r+b') as fp:
663 667 fp.seek(docket.data_size)
664 668 assert fp.tell() == docket.data_size
665 669 written = fp.write(packed)
666 670 if written is not None: # py2 may return None
667 671 assert written == len(packed), (written, len(packed))
668 672 docket.data_size += len(packed)
669 673 docket.parents = self.parents()
670 674 docket.tree_metadata = meta
671 675 st.write(docket.serialize())
672 676 st.close()
673 677 else:
674 678 self.write_v2_no_append(tr, st, meta, packed)
675 679 # Reload from the newly-written file
676 680 util.clearcachedproperty(self, b"_map")
677 681 self._dirtyparents = False
678 682
679 683 ### code related to maintaining and accessing "extra" property
680 684 # (e.g. "has_dir")
681 685
682 686 @propertycache
683 687 def filefoldmap(self):
684 688 """Returns a dictionary mapping normalized case paths to their
685 689 non-normalized versions.
686 690 """
687 691 return self._map.filefoldmapasdict()
688 692
689 693 def hastrackeddir(self, d):
690 694 return self._map.hastrackeddir(d)
691 695
692 696 def hasdir(self, d):
693 697 return self._map.hasdir(d)
694 698
695 699 @propertycache
696 700 def dirfoldmap(self):
697 701 f = {}
698 702 normcase = util.normcase
699 703 for name in self._map.tracked_dirs():
700 704 f[normcase(name)] = name
701 705 return f
702 706
703 707 ### code related to manipulation of entries and copy-sources
704 708
705 709 def set_tracked(self, f):
706 710 return self._map.set_tracked(f)
707 711
708 712 def set_untracked(self, f):
709 713 return self._map.set_untracked(f)
710 714
711 715 def set_clean(self, filename, mode, size, mtime):
712 716 self._map.set_clean(filename, mode, size, mtime)
713 717
714 718 def set_possibly_dirty(self, f):
715 719 self._map.set_possibly_dirty(f)
716 720
717 721 def reset_state(
718 722 self,
719 723 filename,
720 724 wc_tracked=False,
721 725 p1_tracked=False,
722 726 p2_info=False,
723 727 has_meaningful_mtime=True,
724 728 parentfiledata=None,
725 729 ):
726 730 return self._map.reset_state(
727 731 filename,
728 732 wc_tracked,
729 733 p1_tracked,
730 734 p2_info,
731 735 has_meaningful_mtime,
732 736 parentfiledata,
733 737 )
@@ -1,1929 +1,1939 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::FastHashbrownMap as FastHashMap;
26 26 use crate::PatternFileWarning;
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 #[derive(Debug, PartialEq, Eq)]
35 35 /// Version of the on-disk format
36 36 pub enum DirstateVersion {
37 37 V1,
38 38 V2,
39 39 }
40 40
41 41 #[derive(Debug, PartialEq, Eq)]
42 42 pub enum DirstateMapWriteMode {
43 43 Auto,
44 44 ForceNewDataFile,
45 45 ForceAppend,
46 46 }
47 47
48 48 #[derive(Debug)]
49 49 pub struct DirstateMap<'on_disk> {
50 50 /// Contents of the `.hg/dirstate` file
51 51 pub(super) on_disk: &'on_disk [u8],
52 52
53 53 pub(super) root: ChildNodes<'on_disk>,
54 54
55 55 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
56 56 pub(super) nodes_with_entry_count: u32,
57 57
58 58 /// Number of nodes anywhere in the tree that have
59 59 /// `.copy_source.is_some()`.
60 60 pub(super) nodes_with_copy_source_count: u32,
61 61
62 62 /// See on_disk::Header
63 63 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
64 64
65 65 /// How many bytes of `on_disk` are not used anymore
66 66 pub(super) unreachable_bytes: u32,
67 67
68 68 /// Size of the data used to first load this `DirstateMap`. Used in case
69 /// we need to write some new metadata, but no new data on disk.
69 /// we need to write some new metadata, but no new data on disk,
70 /// as well as to detect writes that have happened in another process
71 /// since first read.
70 72 pub(super) old_data_size: usize,
71 73
74 /// UUID used when first loading this `DirstateMap`. Used to check if
75 /// the UUID has been changed by another process since first read.
76 /// Can be `None` if using dirstate v1 or if it's a brand new dirstate.
77 pub(super) old_uuid: Option<Vec<u8>>,
78
72 79 pub(super) dirstate_version: DirstateVersion,
73 80
74 81 /// Controlled by config option `devel.dirstate.v2.data_update_mode`
75 82 pub(super) write_mode: DirstateMapWriteMode,
76 83 }
77 84
78 85 /// Using a plain `HgPathBuf` of the full path from the repository root as a
79 86 /// map key would also work: all paths in a given map have the same parent
80 87 /// path, so comparing full paths gives the same result as comparing base
81 88 /// names. However `HashMap` would waste time always re-hashing the same
82 89 /// string prefix.
83 90 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
84 91
85 92 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
86 93 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
87 94 #[derive(Debug)]
88 95 pub(super) enum BorrowedPath<'tree, 'on_disk> {
89 96 InMemory(&'tree HgPathBuf),
90 97 OnDisk(&'on_disk HgPath),
91 98 }
92 99
93 100 #[derive(Debug)]
94 101 pub(super) enum ChildNodes<'on_disk> {
95 102 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
96 103 OnDisk(&'on_disk [on_disk::Node]),
97 104 }
98 105
99 106 #[derive(Debug)]
100 107 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
101 108 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
102 109 OnDisk(&'on_disk [on_disk::Node]),
103 110 }
104 111
105 112 #[derive(Debug)]
106 113 pub(super) enum NodeRef<'tree, 'on_disk> {
107 114 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
108 115 OnDisk(&'on_disk on_disk::Node),
109 116 }
110 117
111 118 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
112 119 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
113 120 match *self {
114 121 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
115 122 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
116 123 }
117 124 }
118 125 }
119 126
120 127 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
121 128 type Target = HgPath;
122 129
123 130 fn deref(&self) -> &HgPath {
124 131 match *self {
125 132 BorrowedPath::InMemory(in_memory) => in_memory,
126 133 BorrowedPath::OnDisk(on_disk) => on_disk,
127 134 }
128 135 }
129 136 }
130 137
131 138 impl Default for ChildNodes<'_> {
132 139 fn default() -> Self {
133 140 ChildNodes::InMemory(Default::default())
134 141 }
135 142 }
136 143
137 144 impl<'on_disk> ChildNodes<'on_disk> {
138 145 pub(super) fn as_ref<'tree>(
139 146 &'tree self,
140 147 ) -> ChildNodesRef<'tree, 'on_disk> {
141 148 match self {
142 149 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
143 150 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
144 151 }
145 152 }
146 153
147 154 pub(super) fn is_empty(&self) -> bool {
148 155 match self {
149 156 ChildNodes::InMemory(nodes) => nodes.is_empty(),
150 157 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
151 158 }
152 159 }
153 160
154 161 fn make_mut(
155 162 &mut self,
156 163 on_disk: &'on_disk [u8],
157 164 unreachable_bytes: &mut u32,
158 165 ) -> Result<
159 166 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
160 167 DirstateV2ParseError,
161 168 > {
162 169 match self {
163 170 ChildNodes::InMemory(nodes) => Ok(nodes),
164 171 ChildNodes::OnDisk(nodes) => {
165 172 *unreachable_bytes +=
166 173 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
167 174 let nodes = nodes
168 175 .iter()
169 176 .map(|node| {
170 177 Ok((
171 178 node.path(on_disk)?,
172 179 node.to_in_memory_node(on_disk)?,
173 180 ))
174 181 })
175 182 .collect::<Result<_, _>>()?;
176 183 *self = ChildNodes::InMemory(nodes);
177 184 match self {
178 185 ChildNodes::InMemory(nodes) => Ok(nodes),
179 186 ChildNodes::OnDisk(_) => unreachable!(),
180 187 }
181 188 }
182 189 }
183 190 }
184 191 }
185 192
186 193 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
187 194 pub(super) fn get(
188 195 &self,
189 196 base_name: &HgPath,
190 197 on_disk: &'on_disk [u8],
191 198 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
192 199 match self {
193 200 ChildNodesRef::InMemory(nodes) => Ok(nodes
194 201 .get_key_value(base_name)
195 202 .map(|(k, v)| NodeRef::InMemory(k, v))),
196 203 ChildNodesRef::OnDisk(nodes) => {
197 204 let mut parse_result = Ok(());
198 205 let search_result = nodes.binary_search_by(|node| {
199 206 match node.base_name(on_disk) {
200 207 Ok(node_base_name) => node_base_name.cmp(base_name),
201 208 Err(e) => {
202 209 parse_result = Err(e);
203 210 // Dummy comparison result, `search_result` won’t
204 211 // be used since `parse_result` is an error
205 212 std::cmp::Ordering::Equal
206 213 }
207 214 }
208 215 });
209 216 parse_result.map(|()| {
210 217 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
211 218 })
212 219 }
213 220 }
214 221 }
215 222
216 223 /// Iterate in undefined order
217 224 pub(super) fn iter(
218 225 &self,
219 226 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
220 227 match self {
221 228 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
222 229 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
223 230 ),
224 231 ChildNodesRef::OnDisk(nodes) => {
225 232 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
226 233 }
227 234 }
228 235 }
229 236
230 237 /// Iterate in parallel in undefined order
231 238 pub(super) fn par_iter(
232 239 &self,
233 240 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
234 241 {
235 242 use rayon::prelude::*;
236 243 match self {
237 244 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
238 245 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
239 246 ),
240 247 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
241 248 nodes.par_iter().map(NodeRef::OnDisk),
242 249 ),
243 250 }
244 251 }
245 252
246 253 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
247 254 match self {
248 255 ChildNodesRef::InMemory(nodes) => {
249 256 let mut vec: Vec<_> = nodes
250 257 .iter()
251 258 .map(|(k, v)| NodeRef::InMemory(k, v))
252 259 .collect();
253 260 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
254 261 match node {
255 262 NodeRef::InMemory(path, _node) => path.base_name(),
256 263 NodeRef::OnDisk(_) => unreachable!(),
257 264 }
258 265 }
259 266 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
260 267 // value: https://github.com/rust-lang/rust/issues/34162
261 268 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
262 269 vec
263 270 }
264 271 ChildNodesRef::OnDisk(nodes) => {
265 272 // Nodes on disk are already sorted
266 273 nodes.iter().map(NodeRef::OnDisk).collect()
267 274 }
268 275 }
269 276 }
270 277 }
271 278
272 279 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
273 280 pub(super) fn full_path(
274 281 &self,
275 282 on_disk: &'on_disk [u8],
276 283 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
277 284 match self {
278 285 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
279 286 NodeRef::OnDisk(node) => node.full_path(on_disk),
280 287 }
281 288 }
282 289
283 290 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
284 291 /// HgPath>` detached from `'tree`
285 292 pub(super) fn full_path_borrowed(
286 293 &self,
287 294 on_disk: &'on_disk [u8],
288 295 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
289 296 match self {
290 297 NodeRef::InMemory(path, _node) => match path.full_path() {
291 298 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
292 299 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
293 300 },
294 301 NodeRef::OnDisk(node) => {
295 302 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
296 303 }
297 304 }
298 305 }
299 306
300 307 pub(super) fn base_name(
301 308 &self,
302 309 on_disk: &'on_disk [u8],
303 310 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
304 311 match self {
305 312 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
306 313 NodeRef::OnDisk(node) => node.base_name(on_disk),
307 314 }
308 315 }
309 316
310 317 pub(super) fn children(
311 318 &self,
312 319 on_disk: &'on_disk [u8],
313 320 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
314 321 match self {
315 322 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
316 323 NodeRef::OnDisk(node) => {
317 324 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
318 325 }
319 326 }
320 327 }
321 328
322 329 pub(super) fn has_copy_source(&self) -> bool {
323 330 match self {
324 331 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
325 332 NodeRef::OnDisk(node) => node.has_copy_source(),
326 333 }
327 334 }
328 335
329 336 pub(super) fn copy_source(
330 337 &self,
331 338 on_disk: &'on_disk [u8],
332 339 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
333 340 match self {
334 341 NodeRef::InMemory(_path, node) => {
335 342 Ok(node.copy_source.as_ref().map(|s| &**s))
336 343 }
337 344 NodeRef::OnDisk(node) => node.copy_source(on_disk),
338 345 }
339 346 }
340 347 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
341 348 /// HgPath>` detached from `'tree`
342 349 pub(super) fn copy_source_borrowed(
343 350 &self,
344 351 on_disk: &'on_disk [u8],
345 352 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
346 353 {
347 354 Ok(match self {
348 355 NodeRef::InMemory(_path, node) => {
349 356 node.copy_source.as_ref().map(|source| match source {
350 357 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
351 358 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
352 359 })
353 360 }
354 361 NodeRef::OnDisk(node) => node
355 362 .copy_source(on_disk)?
356 363 .map(|source| BorrowedPath::OnDisk(source)),
357 364 })
358 365 }
359 366
360 367 pub(super) fn entry(
361 368 &self,
362 369 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
363 370 match self {
364 371 NodeRef::InMemory(_path, node) => {
365 372 Ok(node.data.as_entry().copied())
366 373 }
367 374 NodeRef::OnDisk(node) => node.entry(),
368 375 }
369 376 }
370 377
371 378 pub(super) fn cached_directory_mtime(
372 379 &self,
373 380 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
374 381 match self {
375 382 NodeRef::InMemory(_path, node) => Ok(match node.data {
376 383 NodeData::CachedDirectory { mtime } => Some(mtime),
377 384 _ => None,
378 385 }),
379 386 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
380 387 }
381 388 }
382 389
383 390 pub(super) fn descendants_with_entry_count(&self) -> u32 {
384 391 match self {
385 392 NodeRef::InMemory(_path, node) => {
386 393 node.descendants_with_entry_count
387 394 }
388 395 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
389 396 }
390 397 }
391 398
392 399 pub(super) fn tracked_descendants_count(&self) -> u32 {
393 400 match self {
394 401 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
395 402 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
396 403 }
397 404 }
398 405 }
399 406
400 407 /// Represents a file or a directory
401 408 #[derive(Default, Debug)]
402 409 pub(super) struct Node<'on_disk> {
403 410 pub(super) data: NodeData,
404 411
405 412 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
406 413
407 414 pub(super) children: ChildNodes<'on_disk>,
408 415
409 416 /// How many (non-inclusive) descendants of this node have an entry.
410 417 pub(super) descendants_with_entry_count: u32,
411 418
412 419 /// How many (non-inclusive) descendants of this node have an entry whose
413 420 /// state is "tracked".
414 421 pub(super) tracked_descendants_count: u32,
415 422 }
416 423
417 424 #[derive(Debug)]
418 425 pub(super) enum NodeData {
419 426 Entry(DirstateEntry),
420 427 CachedDirectory { mtime: TruncatedTimestamp },
421 428 None,
422 429 }
423 430
424 431 impl Default for NodeData {
425 432 fn default() -> Self {
426 433 NodeData::None
427 434 }
428 435 }
429 436
430 437 impl NodeData {
431 438 fn has_entry(&self) -> bool {
432 439 match self {
433 440 NodeData::Entry(_) => true,
434 441 _ => false,
435 442 }
436 443 }
437 444
438 445 fn as_entry(&self) -> Option<&DirstateEntry> {
439 446 match self {
440 447 NodeData::Entry(entry) => Some(entry),
441 448 _ => None,
442 449 }
443 450 }
444 451
445 452 fn as_entry_mut(&mut self) -> Option<&mut DirstateEntry> {
446 453 match self {
447 454 NodeData::Entry(entry) => Some(entry),
448 455 _ => None,
449 456 }
450 457 }
451 458 }
452 459
453 460 impl<'on_disk> DirstateMap<'on_disk> {
454 461 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
455 462 Self {
456 463 on_disk,
457 464 root: ChildNodes::default(),
458 465 nodes_with_entry_count: 0,
459 466 nodes_with_copy_source_count: 0,
460 467 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
461 468 unreachable_bytes: 0,
462 469 old_data_size: 0,
470 old_uuid: None,
463 471 dirstate_version: DirstateVersion::V1,
464 472 write_mode: DirstateMapWriteMode::Auto,
465 473 }
466 474 }
467 475
468 476 #[timed]
469 477 pub fn new_v2(
470 478 on_disk: &'on_disk [u8],
471 479 data_size: usize,
472 480 metadata: &[u8],
481 uuid: Vec<u8>,
473 482 ) -> Result<Self, DirstateError> {
474 483 if let Some(data) = on_disk.get(..data_size) {
475 Ok(on_disk::read(data, metadata)?)
484 Ok(on_disk::read(data, metadata, uuid)?)
476 485 } else {
477 486 Err(DirstateV2ParseError::new("not enough bytes on disk").into())
478 487 }
479 488 }
480 489
481 490 #[timed]
482 491 pub fn new_v1(
483 492 on_disk: &'on_disk [u8],
484 493 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
485 494 let mut map = Self::empty(on_disk);
486 495 if map.on_disk.is_empty() {
487 496 return Ok((map, None));
488 497 }
489 498
490 499 let parents = parse_dirstate_entries(
491 500 map.on_disk,
492 501 |path, entry, copy_source| {
493 502 let tracked = entry.tracked();
494 503 let node = Self::get_or_insert_node_inner(
495 504 map.on_disk,
496 505 &mut map.unreachable_bytes,
497 506 &mut map.root,
498 507 path,
499 508 WithBasename::to_cow_borrowed,
500 509 |ancestor| {
501 510 if tracked {
502 511 ancestor.tracked_descendants_count += 1
503 512 }
504 513 ancestor.descendants_with_entry_count += 1
505 514 },
506 515 )?;
507 516 assert!(
508 517 !node.data.has_entry(),
509 518 "duplicate dirstate entry in read"
510 519 );
511 520 assert!(
512 521 node.copy_source.is_none(),
513 522 "duplicate dirstate entry in read"
514 523 );
515 524 node.data = NodeData::Entry(*entry);
516 525 node.copy_source = copy_source.map(Cow::Borrowed);
517 526 map.nodes_with_entry_count += 1;
518 527 if copy_source.is_some() {
519 528 map.nodes_with_copy_source_count += 1
520 529 }
521 530 Ok(())
522 531 },
523 532 )?;
524 533 let parents = Some(parents.clone());
525 534
526 535 Ok((map, parents))
527 536 }
528 537
529 538 /// Assuming dirstate-v2 format, returns whether the next write should
530 539 /// append to the existing data file that contains `self.on_disk` (true),
531 540 /// or create a new data file from scratch (false).
532 541 pub(super) fn write_should_append(&self) -> bool {
533 542 match self.write_mode {
534 543 DirstateMapWriteMode::ForceAppend => true,
535 544 DirstateMapWriteMode::ForceNewDataFile => false,
536 545 DirstateMapWriteMode::Auto => {
537 546 let ratio =
538 547 self.unreachable_bytes as f32 / self.on_disk.len() as f32;
539 548 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
540 549 }
541 550 }
542 551 }
543 552
544 553 fn get_node<'tree>(
545 554 &'tree self,
546 555 path: &HgPath,
547 556 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
548 557 let mut children = self.root.as_ref();
549 558 let mut components = path.components();
550 559 let mut component =
551 560 components.next().expect("expected at least one components");
552 561 loop {
553 562 if let Some(child) = children.get(component, self.on_disk)? {
554 563 if let Some(next_component) = components.next() {
555 564 component = next_component;
556 565 children = child.children(self.on_disk)?;
557 566 } else {
558 567 return Ok(Some(child));
559 568 }
560 569 } else {
561 570 return Ok(None);
562 571 }
563 572 }
564 573 }
565 574
566 575 /// Returns a mutable reference to the node at `path` if it exists
567 576 ///
568 577 /// `each_ancestor` is a callback that is called for each ancestor node
569 578 /// when descending the tree. It is used to keep the different counters
570 579 /// of the `DirstateMap` up-to-date.
571 580 fn get_node_mut<'tree>(
572 581 &'tree mut self,
573 582 path: &HgPath,
574 583 each_ancestor: impl FnMut(&mut Node),
575 584 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
576 585 Self::get_node_mut_inner(
577 586 self.on_disk,
578 587 &mut self.unreachable_bytes,
579 588 &mut self.root,
580 589 path,
581 590 each_ancestor,
582 591 )
583 592 }
584 593
585 594 /// Lower-level version of `get_node_mut`.
586 595 ///
587 596 /// This takes `root` instead of `&mut self` so that callers can mutate
588 597 /// other fields while the returned borrow is still valid.
589 598 ///
590 599 /// `each_ancestor` is a callback that is called for each ancestor node
591 600 /// when descending the tree. It is used to keep the different counters
592 601 /// of the `DirstateMap` up-to-date.
593 602 fn get_node_mut_inner<'tree>(
594 603 on_disk: &'on_disk [u8],
595 604 unreachable_bytes: &mut u32,
596 605 root: &'tree mut ChildNodes<'on_disk>,
597 606 path: &HgPath,
598 607 mut each_ancestor: impl FnMut(&mut Node),
599 608 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
600 609 let mut children = root;
601 610 let mut components = path.components();
602 611 let mut component =
603 612 components.next().expect("expected at least one components");
604 613 loop {
605 614 if let Some(child) = children
606 615 .make_mut(on_disk, unreachable_bytes)?
607 616 .get_mut(component)
608 617 {
609 618 if let Some(next_component) = components.next() {
610 619 each_ancestor(child);
611 620 component = next_component;
612 621 children = &mut child.children;
613 622 } else {
614 623 return Ok(Some(child));
615 624 }
616 625 } else {
617 626 return Ok(None);
618 627 }
619 628 }
620 629 }
621 630
622 631 /// Get a mutable reference to the node at `path`, creating it if it does
623 632 /// not exist.
624 633 ///
625 634 /// `each_ancestor` is a callback that is called for each ancestor node
626 635 /// when descending the tree. It is used to keep the different counters
627 636 /// of the `DirstateMap` up-to-date.
628 637 fn get_or_insert_node<'tree, 'path>(
629 638 &'tree mut self,
630 639 path: &'path HgPath,
631 640 each_ancestor: impl FnMut(&mut Node),
632 641 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
633 642 Self::get_or_insert_node_inner(
634 643 self.on_disk,
635 644 &mut self.unreachable_bytes,
636 645 &mut self.root,
637 646 path,
638 647 WithBasename::to_cow_owned,
639 648 each_ancestor,
640 649 )
641 650 }
642 651
643 652 /// Lower-level version of `get_or_insert_node_inner`, which is used when
644 653 /// parsing disk data to remove allocations for new nodes.
645 654 fn get_or_insert_node_inner<'tree, 'path>(
646 655 on_disk: &'on_disk [u8],
647 656 unreachable_bytes: &mut u32,
648 657 root: &'tree mut ChildNodes<'on_disk>,
649 658 path: &'path HgPath,
650 659 to_cow: impl Fn(
651 660 WithBasename<&'path HgPath>,
652 661 ) -> WithBasename<Cow<'on_disk, HgPath>>,
653 662 mut each_ancestor: impl FnMut(&mut Node),
654 663 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
655 664 let mut child_nodes = root;
656 665 let mut inclusive_ancestor_paths =
657 666 WithBasename::inclusive_ancestors_of(path);
658 667 let mut ancestor_path = inclusive_ancestor_paths
659 668 .next()
660 669 .expect("expected at least one inclusive ancestor");
661 670 loop {
662 671 let (_, child_node) = child_nodes
663 672 .make_mut(on_disk, unreachable_bytes)?
664 673 .raw_entry_mut()
665 674 .from_key(ancestor_path.base_name())
666 675 .or_insert_with(|| (to_cow(ancestor_path), Node::default()));
667 676 if let Some(next) = inclusive_ancestor_paths.next() {
668 677 each_ancestor(child_node);
669 678 ancestor_path = next;
670 679 child_nodes = &mut child_node.children;
671 680 } else {
672 681 return Ok(child_node);
673 682 }
674 683 }
675 684 }
676 685
677 686 fn reset_state(
678 687 &mut self,
679 688 filename: &HgPath,
680 689 old_entry_opt: Option<DirstateEntry>,
681 690 wc_tracked: bool,
682 691 p1_tracked: bool,
683 692 p2_info: bool,
684 693 has_meaningful_mtime: bool,
685 694 parent_file_data_opt: Option<ParentFileData>,
686 695 ) -> Result<(), DirstateError> {
687 696 let (had_entry, was_tracked) = match old_entry_opt {
688 697 Some(old_entry) => (true, old_entry.tracked()),
689 698 None => (false, false),
690 699 };
691 700 let node = self.get_or_insert_node(filename, |ancestor| {
692 701 if !had_entry {
693 702 ancestor.descendants_with_entry_count += 1;
694 703 }
695 704 if was_tracked {
696 705 if !wc_tracked {
697 706 ancestor.tracked_descendants_count = ancestor
698 707 .tracked_descendants_count
699 708 .checked_sub(1)
700 709 .expect("tracked count to be >= 0");
701 710 }
702 711 } else {
703 712 if wc_tracked {
704 713 ancestor.tracked_descendants_count += 1;
705 714 }
706 715 }
707 716 })?;
708 717
709 718 let v2_data = if let Some(parent_file_data) = parent_file_data_opt {
710 719 DirstateV2Data {
711 720 wc_tracked,
712 721 p1_tracked,
713 722 p2_info,
714 723 mode_size: parent_file_data.mode_size,
715 724 mtime: if has_meaningful_mtime {
716 725 parent_file_data.mtime
717 726 } else {
718 727 None
719 728 },
720 729 ..Default::default()
721 730 }
722 731 } else {
723 732 DirstateV2Data {
724 733 wc_tracked,
725 734 p1_tracked,
726 735 p2_info,
727 736 ..Default::default()
728 737 }
729 738 };
730 739 node.data = NodeData::Entry(DirstateEntry::from_v2_data(v2_data));
731 740 if !had_entry {
732 741 self.nodes_with_entry_count += 1;
733 742 }
734 743 Ok(())
735 744 }
736 745
737 746 fn set_tracked(
738 747 &mut self,
739 748 filename: &HgPath,
740 749 old_entry_opt: Option<DirstateEntry>,
741 750 ) -> Result<bool, DirstateV2ParseError> {
742 751 let was_tracked = old_entry_opt.map_or(false, |e| e.tracked());
743 752 let had_entry = old_entry_opt.is_some();
744 753 let tracked_count_increment = if was_tracked { 0 } else { 1 };
745 754 let mut new = false;
746 755
747 756 let node = self.get_or_insert_node(filename, |ancestor| {
748 757 if !had_entry {
749 758 ancestor.descendants_with_entry_count += 1;
750 759 }
751 760
752 761 ancestor.tracked_descendants_count += tracked_count_increment;
753 762 })?;
754 763 if let Some(old_entry) = old_entry_opt {
755 764 let mut e = old_entry.clone();
756 765 if e.tracked() {
757 766 // XXX
758 767 // This is probably overkill for more case, but we need this to
759 768 // fully replace the `normallookup` call with `set_tracked`
760 769 // one. Consider smoothing this in the future.
761 770 e.set_possibly_dirty();
762 771 } else {
763 772 new = true;
764 773 e.set_tracked();
765 774 }
766 775 node.data = NodeData::Entry(e)
767 776 } else {
768 777 node.data = NodeData::Entry(DirstateEntry::new_tracked());
769 778 self.nodes_with_entry_count += 1;
770 779 new = true;
771 780 };
772 781 Ok(new)
773 782 }
774 783
775 784 /// Set a node as untracked in the dirstate.
776 785 ///
777 786 /// It is the responsibility of the caller to remove the copy source and/or
778 787 /// the entry itself if appropriate.
779 788 ///
780 789 /// # Panics
781 790 ///
782 791 /// Panics if the node does not exist.
783 792 fn set_untracked(
784 793 &mut self,
785 794 filename: &HgPath,
786 795 old_entry: DirstateEntry,
787 796 ) -> Result<(), DirstateV2ParseError> {
788 797 let node = self
789 798 .get_node_mut(filename, |ancestor| {
790 799 ancestor.tracked_descendants_count = ancestor
791 800 .tracked_descendants_count
792 801 .checked_sub(1)
793 802 .expect("tracked_descendants_count should be >= 0");
794 803 })?
795 804 .expect("node should exist");
796 805 let mut new_entry = old_entry.clone();
797 806 new_entry.set_untracked();
798 807 node.data = NodeData::Entry(new_entry);
799 808 Ok(())
800 809 }
801 810
802 811 /// Set a node as clean in the dirstate.
803 812 ///
804 813 /// It is the responsibility of the caller to remove the copy source.
805 814 ///
806 815 /// # Panics
807 816 ///
808 817 /// Panics if the node does not exist.
809 818 fn set_clean(
810 819 &mut self,
811 820 filename: &HgPath,
812 821 old_entry: DirstateEntry,
813 822 mode: u32,
814 823 size: u32,
815 824 mtime: TruncatedTimestamp,
816 825 ) -> Result<(), DirstateError> {
817 826 let node = self
818 827 .get_node_mut(filename, |ancestor| {
819 828 if !old_entry.tracked() {
820 829 ancestor.tracked_descendants_count += 1;
821 830 }
822 831 })?
823 832 .expect("node should exist");
824 833 let mut new_entry = old_entry.clone();
825 834 new_entry.set_clean(mode, size, mtime);
826 835 node.data = NodeData::Entry(new_entry);
827 836 Ok(())
828 837 }
829 838
830 839 /// Set a node as possibly dirty in the dirstate.
831 840 ///
832 841 /// # Panics
833 842 ///
834 843 /// Panics if the node does not exist.
835 844 fn set_possibly_dirty(
836 845 &mut self,
837 846 filename: &HgPath,
838 847 ) -> Result<(), DirstateError> {
839 848 let node = self
840 849 .get_node_mut(filename, |_ancestor| {})?
841 850 .expect("node should exist");
842 851 let entry = node.data.as_entry_mut().expect("entry should exist");
843 852 entry.set_possibly_dirty();
844 853 node.data = NodeData::Entry(*entry);
845 854 Ok(())
846 855 }
847 856
848 857 /// Clears the cached mtime for the (potential) folder at `path`.
849 858 pub(super) fn clear_cached_mtime(
850 859 &mut self,
851 860 path: &HgPath,
852 861 ) -> Result<(), DirstateV2ParseError> {
853 862 let node = match self.get_node_mut(path, |_ancestor| {})? {
854 863 Some(node) => node,
855 864 None => return Ok(()),
856 865 };
857 866 if let NodeData::CachedDirectory { .. } = &node.data {
858 867 node.data = NodeData::None
859 868 }
860 869 Ok(())
861 870 }
862 871
863 872 /// Sets the cached mtime for the (potential) folder at `path`.
864 873 pub(super) fn set_cached_mtime(
865 874 &mut self,
866 875 path: &HgPath,
867 876 mtime: TruncatedTimestamp,
868 877 ) -> Result<(), DirstateV2ParseError> {
869 878 let node = match self.get_node_mut(path, |_ancestor| {})? {
870 879 Some(node) => node,
871 880 None => return Ok(()),
872 881 };
873 882 match &node.data {
874 883 NodeData::Entry(_) => {} // Don’t overwrite an entry
875 884 NodeData::CachedDirectory { .. } | NodeData::None => {
876 885 node.data = NodeData::CachedDirectory { mtime }
877 886 }
878 887 }
879 888 Ok(())
880 889 }
881 890
882 891 fn iter_nodes<'tree>(
883 892 &'tree self,
884 893 ) -> impl Iterator<
885 894 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
886 895 > + 'tree {
887 896 // Depth first tree traversal.
888 897 //
889 898 // If we could afford internal iteration and recursion,
890 899 // this would look like:
891 900 //
892 901 // ```
893 902 // fn traverse_children(
894 903 // children: &ChildNodes,
895 904 // each: &mut impl FnMut(&Node),
896 905 // ) {
897 906 // for child in children.values() {
898 907 // traverse_children(&child.children, each);
899 908 // each(child);
900 909 // }
901 910 // }
902 911 // ```
903 912 //
904 913 // However we want an external iterator and therefore can’t use the
905 914 // call stack. Use an explicit stack instead:
906 915 let mut stack = Vec::new();
907 916 let mut iter = self.root.as_ref().iter();
908 917 std::iter::from_fn(move || {
909 918 while let Some(child_node) = iter.next() {
910 919 let children = match child_node.children(self.on_disk) {
911 920 Ok(children) => children,
912 921 Err(error) => return Some(Err(error)),
913 922 };
914 923 // Pseudo-recursion
915 924 let new_iter = children.iter();
916 925 let old_iter = std::mem::replace(&mut iter, new_iter);
917 926 stack.push((child_node, old_iter));
918 927 }
919 928 // Found the end of a `children.iter()` iterator.
920 929 if let Some((child_node, next_iter)) = stack.pop() {
921 930 // "Return" from pseudo-recursion by restoring state from the
922 931 // explicit stack
923 932 iter = next_iter;
924 933
925 934 Some(Ok(child_node))
926 935 } else {
927 936 // Reached the bottom of the stack, we’re done
928 937 None
929 938 }
930 939 })
931 940 }
932 941
933 942 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
934 943 if let Cow::Borrowed(path) = path {
935 944 *unreachable_bytes += path.len() as u32
936 945 }
937 946 }
938 947
939 948 pub(crate) fn set_write_mode(&mut self, write_mode: DirstateMapWriteMode) {
940 949 self.write_mode = write_mode;
941 950 }
942 951 }
943 952
944 953 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
945 954 ///
946 955 /// The callback is only called for incoming `Ok` values. Errors are passed
947 956 /// through as-is. In order to let it use the `?` operator the callback is
948 957 /// expected to return a `Result` of `Option`, instead of an `Option` of
949 958 /// `Result`.
950 959 fn filter_map_results<'a, I, F, A, B, E>(
951 960 iter: I,
952 961 f: F,
953 962 ) -> impl Iterator<Item = Result<B, E>> + 'a
954 963 where
955 964 I: Iterator<Item = Result<A, E>> + 'a,
956 965 F: Fn(A) -> Result<Option<B>, E> + 'a,
957 966 {
958 967 iter.filter_map(move |result| match result {
959 968 Ok(node) => f(node).transpose(),
960 969 Err(e) => Some(Err(e)),
961 970 })
962 971 }
963 972
964 973 impl OwningDirstateMap {
965 974 pub fn clear(&mut self) {
966 975 self.with_dmap_mut(|map| {
967 976 map.root = Default::default();
968 977 map.nodes_with_entry_count = 0;
969 978 map.nodes_with_copy_source_count = 0;
970 979 });
971 980 }
972 981
973 982 pub fn set_tracked(
974 983 &mut self,
975 984 filename: &HgPath,
976 985 ) -> Result<bool, DirstateV2ParseError> {
977 986 let old_entry_opt = self.get(filename)?;
978 987 self.with_dmap_mut(|map| map.set_tracked(filename, old_entry_opt))
979 988 }
980 989
981 990 pub fn set_untracked(
982 991 &mut self,
983 992 filename: &HgPath,
984 993 ) -> Result<bool, DirstateError> {
985 994 let old_entry_opt = self.get(filename)?;
986 995 match old_entry_opt {
987 996 None => Ok(false),
988 997 Some(old_entry) => {
989 998 if !old_entry.tracked() {
990 999 // `DirstateMap::set_untracked` is not a noop if
991 1000 // already not tracked as it will decrement the
992 1001 // tracked counters while going down.
993 1002 return Ok(true);
994 1003 }
995 1004 if old_entry.added() {
996 1005 // Untracking an "added" entry will just result in a
997 1006 // worthless entry (and other parts of the code will
998 1007 // complain about it), just drop it entirely.
999 1008 self.drop_entry_and_copy_source(filename)?;
1000 1009 return Ok(true);
1001 1010 }
1002 1011 if !old_entry.p2_info() {
1003 1012 self.copy_map_remove(filename)?;
1004 1013 }
1005 1014
1006 1015 self.with_dmap_mut(|map| {
1007 1016 map.set_untracked(filename, old_entry)?;
1008 1017 Ok(true)
1009 1018 })
1010 1019 }
1011 1020 }
1012 1021 }
1013 1022
1014 1023 pub fn set_clean(
1015 1024 &mut self,
1016 1025 filename: &HgPath,
1017 1026 mode: u32,
1018 1027 size: u32,
1019 1028 mtime: TruncatedTimestamp,
1020 1029 ) -> Result<(), DirstateError> {
1021 1030 let old_entry = match self.get(filename)? {
1022 1031 None => {
1023 1032 return Err(
1024 1033 DirstateMapError::PathNotFound(filename.into()).into()
1025 1034 )
1026 1035 }
1027 1036 Some(e) => e,
1028 1037 };
1029 1038 self.copy_map_remove(filename)?;
1030 1039 self.with_dmap_mut(|map| {
1031 1040 map.set_clean(filename, old_entry, mode, size, mtime)
1032 1041 })
1033 1042 }
1034 1043
1035 1044 pub fn set_possibly_dirty(
1036 1045 &mut self,
1037 1046 filename: &HgPath,
1038 1047 ) -> Result<(), DirstateError> {
1039 1048 if self.get(filename)?.is_none() {
1040 1049 return Err(DirstateMapError::PathNotFound(filename.into()).into());
1041 1050 }
1042 1051 self.with_dmap_mut(|map| map.set_possibly_dirty(filename))
1043 1052 }
1044 1053
1045 1054 pub fn reset_state(
1046 1055 &mut self,
1047 1056 filename: &HgPath,
1048 1057 wc_tracked: bool,
1049 1058 p1_tracked: bool,
1050 1059 p2_info: bool,
1051 1060 has_meaningful_mtime: bool,
1052 1061 parent_file_data_opt: Option<ParentFileData>,
1053 1062 ) -> Result<(), DirstateError> {
1054 1063 if !(p1_tracked || p2_info || wc_tracked) {
1055 1064 self.drop_entry_and_copy_source(filename)?;
1056 1065 return Ok(());
1057 1066 }
1058 1067 self.copy_map_remove(filename)?;
1059 1068 let old_entry_opt = self.get(filename)?;
1060 1069 self.with_dmap_mut(|map| {
1061 1070 map.reset_state(
1062 1071 filename,
1063 1072 old_entry_opt,
1064 1073 wc_tracked,
1065 1074 p1_tracked,
1066 1075 p2_info,
1067 1076 has_meaningful_mtime,
1068 1077 parent_file_data_opt,
1069 1078 )
1070 1079 })
1071 1080 }
1072 1081
1073 1082 pub fn drop_entry_and_copy_source(
1074 1083 &mut self,
1075 1084 filename: &HgPath,
1076 1085 ) -> Result<(), DirstateError> {
1077 1086 let was_tracked = self.get(filename)?.map_or(false, |e| e.tracked());
1078 1087 struct Dropped {
1079 1088 was_tracked: bool,
1080 1089 had_entry: bool,
1081 1090 had_copy_source: bool,
1082 1091 }
1083 1092
1084 1093 /// If this returns `Ok(Some((dropped, removed)))`, then
1085 1094 ///
1086 1095 /// * `dropped` is about the leaf node that was at `filename`
1087 1096 /// * `removed` is whether this particular level of recursion just
1088 1097 /// removed a node in `nodes`.
1089 1098 fn recur<'on_disk>(
1090 1099 on_disk: &'on_disk [u8],
1091 1100 unreachable_bytes: &mut u32,
1092 1101 nodes: &mut ChildNodes<'on_disk>,
1093 1102 path: &HgPath,
1094 1103 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
1095 1104 let (first_path_component, rest_of_path) =
1096 1105 path.split_first_component();
1097 1106 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
1098 1107 let node = if let Some(node) = nodes.get_mut(first_path_component)
1099 1108 {
1100 1109 node
1101 1110 } else {
1102 1111 return Ok(None);
1103 1112 };
1104 1113 let dropped;
1105 1114 if let Some(rest) = rest_of_path {
1106 1115 if let Some((d, removed)) = recur(
1107 1116 on_disk,
1108 1117 unreachable_bytes,
1109 1118 &mut node.children,
1110 1119 rest,
1111 1120 )? {
1112 1121 dropped = d;
1113 1122 if dropped.had_entry {
1114 1123 node.descendants_with_entry_count = node
1115 1124 .descendants_with_entry_count
1116 1125 .checked_sub(1)
1117 1126 .expect(
1118 1127 "descendants_with_entry_count should be >= 0",
1119 1128 );
1120 1129 }
1121 1130 if dropped.was_tracked {
1122 1131 node.tracked_descendants_count = node
1123 1132 .tracked_descendants_count
1124 1133 .checked_sub(1)
1125 1134 .expect(
1126 1135 "tracked_descendants_count should be >= 0",
1127 1136 );
1128 1137 }
1129 1138
1130 1139 // Directory caches must be invalidated when removing a
1131 1140 // child node
1132 1141 if removed {
1133 1142 if let NodeData::CachedDirectory { .. } = &node.data {
1134 1143 node.data = NodeData::None
1135 1144 }
1136 1145 }
1137 1146 } else {
1138 1147 return Ok(None);
1139 1148 }
1140 1149 } else {
1141 1150 let entry = node.data.as_entry();
1142 1151 let was_tracked = entry.map_or(false, |entry| entry.tracked());
1143 1152 let had_entry = entry.is_some();
1144 1153 if had_entry {
1145 1154 node.data = NodeData::None
1146 1155 }
1147 1156 let mut had_copy_source = false;
1148 1157 if let Some(source) = &node.copy_source {
1149 1158 DirstateMap::count_dropped_path(unreachable_bytes, source);
1150 1159 had_copy_source = true;
1151 1160 node.copy_source = None
1152 1161 }
1153 1162 dropped = Dropped {
1154 1163 was_tracked,
1155 1164 had_entry,
1156 1165 had_copy_source,
1157 1166 };
1158 1167 }
1159 1168 // After recursion, for both leaf (rest_of_path is None) nodes and
1160 1169 // parent nodes, remove a node if it just became empty.
1161 1170 let remove = !node.data.has_entry()
1162 1171 && node.copy_source.is_none()
1163 1172 && node.children.is_empty();
1164 1173 if remove {
1165 1174 let (key, _) =
1166 1175 nodes.remove_entry(first_path_component).unwrap();
1167 1176 DirstateMap::count_dropped_path(
1168 1177 unreachable_bytes,
1169 1178 key.full_path(),
1170 1179 )
1171 1180 }
1172 1181 Ok(Some((dropped, remove)))
1173 1182 }
1174 1183
1175 1184 self.with_dmap_mut(|map| {
1176 1185 if let Some((dropped, _removed)) = recur(
1177 1186 map.on_disk,
1178 1187 &mut map.unreachable_bytes,
1179 1188 &mut map.root,
1180 1189 filename,
1181 1190 )? {
1182 1191 if dropped.had_entry {
1183 1192 map.nodes_with_entry_count = map
1184 1193 .nodes_with_entry_count
1185 1194 .checked_sub(1)
1186 1195 .expect("nodes_with_entry_count should be >= 0");
1187 1196 }
1188 1197 if dropped.had_copy_source {
1189 1198 map.nodes_with_copy_source_count = map
1190 1199 .nodes_with_copy_source_count
1191 1200 .checked_sub(1)
1192 1201 .expect("nodes_with_copy_source_count should be >= 0");
1193 1202 }
1194 1203 } else {
1195 1204 debug_assert!(!was_tracked);
1196 1205 }
1197 1206 Ok(())
1198 1207 })
1199 1208 }
1200 1209
1201 1210 pub fn has_tracked_dir(
1202 1211 &mut self,
1203 1212 directory: &HgPath,
1204 1213 ) -> Result<bool, DirstateError> {
1205 1214 self.with_dmap_mut(|map| {
1206 1215 if let Some(node) = map.get_node(directory)? {
1207 1216 // A node without a `DirstateEntry` was created to hold child
1208 1217 // nodes, and is therefore a directory.
1209 1218 let is_dir = node.entry()?.is_none();
1210 1219 Ok(is_dir && node.tracked_descendants_count() > 0)
1211 1220 } else {
1212 1221 Ok(false)
1213 1222 }
1214 1223 })
1215 1224 }
1216 1225
1217 1226 pub fn has_dir(
1218 1227 &mut self,
1219 1228 directory: &HgPath,
1220 1229 ) -> Result<bool, DirstateError> {
1221 1230 self.with_dmap_mut(|map| {
1222 1231 if let Some(node) = map.get_node(directory)? {
1223 1232 // A node without a `DirstateEntry` was created to hold child
1224 1233 // nodes, and is therefore a directory.
1225 1234 let is_dir = node.entry()?.is_none();
1226 1235 Ok(is_dir && node.descendants_with_entry_count() > 0)
1227 1236 } else {
1228 1237 Ok(false)
1229 1238 }
1230 1239 })
1231 1240 }
1232 1241
1233 1242 #[timed]
1234 1243 pub fn pack_v1(
1235 1244 &self,
1236 1245 parents: DirstateParents,
1237 1246 ) -> Result<Vec<u8>, DirstateError> {
1238 1247 let map = self.get_map();
1239 1248 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1240 1249 // reallocations
1241 1250 let mut size = parents.as_bytes().len();
1242 1251 for node in map.iter_nodes() {
1243 1252 let node = node?;
1244 1253 if node.entry()?.is_some() {
1245 1254 size += packed_entry_size(
1246 1255 node.full_path(map.on_disk)?,
1247 1256 node.copy_source(map.on_disk)?,
1248 1257 );
1249 1258 }
1250 1259 }
1251 1260
1252 1261 let mut packed = Vec::with_capacity(size);
1253 1262 packed.extend(parents.as_bytes());
1254 1263
1255 1264 for node in map.iter_nodes() {
1256 1265 let node = node?;
1257 1266 if let Some(entry) = node.entry()? {
1258 1267 pack_entry(
1259 1268 node.full_path(map.on_disk)?,
1260 1269 &entry,
1261 1270 node.copy_source(map.on_disk)?,
1262 1271 &mut packed,
1263 1272 );
1264 1273 }
1265 1274 }
1266 1275 Ok(packed)
1267 1276 }
1268 1277
1269 1278 /// Returns new data and metadata together with whether that data should be
1270 1279 /// appended to the existing data file whose content is at
1271 1280 /// `map.on_disk` (true), instead of written to a new data file
1272 1281 /// (false), and the previous size of data on disk.
1273 1282 #[timed]
1274 1283 pub fn pack_v2(
1275 1284 &self,
1276 1285 write_mode: DirstateMapWriteMode,
1277 1286 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool, usize), DirstateError>
1278 1287 {
1279 1288 let map = self.get_map();
1280 1289 on_disk::write(map, write_mode)
1281 1290 }
1282 1291
1283 1292 /// `callback` allows the caller to process and do something with the
1284 1293 /// results of the status. This is needed to do so efficiently (i.e.
1285 1294 /// without cloning the `DirstateStatus` object with its paths) because
1286 1295 /// we need to borrow from `Self`.
1287 1296 pub fn with_status<R>(
1288 1297 &mut self,
1289 1298 matcher: &(dyn Matcher + Sync),
1290 1299 root_dir: PathBuf,
1291 1300 ignore_files: Vec<PathBuf>,
1292 1301 options: StatusOptions,
1293 1302 callback: impl for<'r> FnOnce(
1294 1303 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
1295 1304 ) -> R,
1296 1305 ) -> R {
1297 1306 self.with_dmap_mut(|map| {
1298 1307 callback(super::status::status(
1299 1308 map,
1300 1309 matcher,
1301 1310 root_dir,
1302 1311 ignore_files,
1303 1312 options,
1304 1313 ))
1305 1314 })
1306 1315 }
1307 1316
1308 1317 pub fn copy_map_len(&self) -> usize {
1309 1318 let map = self.get_map();
1310 1319 map.nodes_with_copy_source_count as usize
1311 1320 }
1312 1321
1313 1322 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1314 1323 let map = self.get_map();
1315 1324 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1316 1325 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1317 1326 Some((node.full_path(map.on_disk)?, source))
1318 1327 } else {
1319 1328 None
1320 1329 })
1321 1330 }))
1322 1331 }
1323 1332
1324 1333 pub fn copy_map_contains_key(
1325 1334 &self,
1326 1335 key: &HgPath,
1327 1336 ) -> Result<bool, DirstateV2ParseError> {
1328 1337 let map = self.get_map();
1329 1338 Ok(if let Some(node) = map.get_node(key)? {
1330 1339 node.has_copy_source()
1331 1340 } else {
1332 1341 false
1333 1342 })
1334 1343 }
1335 1344
1336 1345 pub fn copy_map_get(
1337 1346 &self,
1338 1347 key: &HgPath,
1339 1348 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1340 1349 let map = self.get_map();
1341 1350 if let Some(node) = map.get_node(key)? {
1342 1351 if let Some(source) = node.copy_source(map.on_disk)? {
1343 1352 return Ok(Some(source));
1344 1353 }
1345 1354 }
1346 1355 Ok(None)
1347 1356 }
1348 1357
1349 1358 pub fn copy_map_remove(
1350 1359 &mut self,
1351 1360 key: &HgPath,
1352 1361 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1353 1362 self.with_dmap_mut(|map| {
1354 1363 let count = &mut map.nodes_with_copy_source_count;
1355 1364 let unreachable_bytes = &mut map.unreachable_bytes;
1356 1365 Ok(DirstateMap::get_node_mut_inner(
1357 1366 map.on_disk,
1358 1367 unreachable_bytes,
1359 1368 &mut map.root,
1360 1369 key,
1361 1370 |_ancestor| {},
1362 1371 )?
1363 1372 .and_then(|node| {
1364 1373 if let Some(source) = &node.copy_source {
1365 1374 *count = count
1366 1375 .checked_sub(1)
1367 1376 .expect("nodes_with_copy_source_count should be >= 0");
1368 1377 DirstateMap::count_dropped_path(unreachable_bytes, source);
1369 1378 }
1370 1379 node.copy_source.take().map(Cow::into_owned)
1371 1380 }))
1372 1381 })
1373 1382 }
1374 1383
1375 1384 pub fn copy_map_insert(
1376 1385 &mut self,
1377 1386 key: &HgPath,
1378 1387 value: &HgPath,
1379 1388 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1380 1389 self.with_dmap_mut(|map| {
1381 1390 let node = map.get_or_insert_node(&key, |_ancestor| {})?;
1382 1391 let had_copy_source = node.copy_source.is_none();
1383 1392 let old = node
1384 1393 .copy_source
1385 1394 .replace(value.to_owned().into())
1386 1395 .map(Cow::into_owned);
1387 1396 if had_copy_source {
1388 1397 map.nodes_with_copy_source_count += 1
1389 1398 }
1390 1399 Ok(old)
1391 1400 })
1392 1401 }
1393 1402
1394 1403 pub fn len(&self) -> usize {
1395 1404 let map = self.get_map();
1396 1405 map.nodes_with_entry_count as usize
1397 1406 }
1398 1407
1399 1408 pub fn contains_key(
1400 1409 &self,
1401 1410 key: &HgPath,
1402 1411 ) -> Result<bool, DirstateV2ParseError> {
1403 1412 Ok(self.get(key)?.is_some())
1404 1413 }
1405 1414
1406 1415 pub fn get(
1407 1416 &self,
1408 1417 key: &HgPath,
1409 1418 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1410 1419 let map = self.get_map();
1411 1420 Ok(if let Some(node) = map.get_node(key)? {
1412 1421 node.entry()?
1413 1422 } else {
1414 1423 None
1415 1424 })
1416 1425 }
1417 1426
1418 1427 pub fn iter(&self) -> StateMapIter<'_> {
1419 1428 let map = self.get_map();
1420 1429 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1421 1430 Ok(if let Some(entry) = node.entry()? {
1422 1431 Some((node.full_path(map.on_disk)?, entry))
1423 1432 } else {
1424 1433 None
1425 1434 })
1426 1435 }))
1427 1436 }
1428 1437
1429 1438 pub fn iter_tracked_dirs(
1430 1439 &mut self,
1431 1440 ) -> Result<
1432 1441 Box<
1433 1442 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1434 1443 + Send
1435 1444 + '_,
1436 1445 >,
1437 1446 DirstateError,
1438 1447 > {
1439 1448 let map = self.get_map();
1440 1449 let on_disk = map.on_disk;
1441 1450 Ok(Box::new(filter_map_results(
1442 1451 map.iter_nodes(),
1443 1452 move |node| {
1444 1453 Ok(if node.tracked_descendants_count() > 0 {
1445 1454 Some(node.full_path(on_disk)?)
1446 1455 } else {
1447 1456 None
1448 1457 })
1449 1458 },
1450 1459 )))
1451 1460 }
1452 1461
1453 1462 /// Only public because it needs to be exposed to the Python layer.
1454 1463 /// It is not the full `setparents` logic, only the parts that mutate the
1455 1464 /// entries.
1456 1465 pub fn setparents_fixup(
1457 1466 &mut self,
1458 1467 ) -> Result<Vec<(HgPathBuf, HgPathBuf)>, DirstateV2ParseError> {
1459 1468 // XXX
1460 1469 // All the copying and re-querying is quite inefficient, but this is
1461 1470 // still a lot better than doing it from Python.
1462 1471 //
1463 1472 // The better solution is to develop a mechanism for `iter_mut`,
1464 1473 // which will be a lot more involved: we're dealing with a lazy,
1465 1474 // append-mostly, tree-like data structure. This will do for now.
1466 1475 let mut copies = vec![];
1467 1476 let mut files_with_p2_info = vec![];
1468 1477 for res in self.iter() {
1469 1478 let (path, entry) = res?;
1470 1479 if entry.p2_info() {
1471 1480 files_with_p2_info.push(path.to_owned())
1472 1481 }
1473 1482 }
1474 1483 self.with_dmap_mut(|map| {
1475 1484 for path in files_with_p2_info.iter() {
1476 1485 let node = map.get_or_insert_node(path, |_| {})?;
1477 1486 let entry =
1478 1487 node.data.as_entry_mut().expect("entry should exist");
1479 1488 entry.drop_merge_data();
1480 1489 if let Some(source) = node.copy_source.take().as_deref() {
1481 1490 copies.push((path.to_owned(), source.to_owned()));
1482 1491 }
1483 1492 }
1484 1493 Ok(copies)
1485 1494 })
1486 1495 }
1487 1496
1488 1497 pub fn debug_iter(
1489 1498 &self,
1490 1499 all: bool,
1491 1500 ) -> Box<
1492 1501 dyn Iterator<
1493 1502 Item = Result<
1494 1503 (&HgPath, (u8, i32, i32, i32)),
1495 1504 DirstateV2ParseError,
1496 1505 >,
1497 1506 > + Send
1498 1507 + '_,
1499 1508 > {
1500 1509 let map = self.get_map();
1501 1510 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1502 1511 let debug_tuple = if let Some(entry) = node.entry()? {
1503 1512 entry.debug_tuple()
1504 1513 } else if !all {
1505 1514 return Ok(None);
1506 1515 } else if let Some(mtime) = node.cached_directory_mtime()? {
1507 1516 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1508 1517 } else {
1509 1518 (b' ', 0, -1, -1)
1510 1519 };
1511 1520 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1512 1521 }))
1513 1522 }
1514 1523 }
1515 1524 #[cfg(test)]
1516 1525 mod tests {
1517 1526 use super::*;
1518 1527
1519 1528 /// Shortcut to return tracked descendants of a path.
1520 1529 /// Panics if the path does not exist.
1521 1530 fn tracked_descendants(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1522 1531 let path = dbg!(HgPath::new(path));
1523 1532 let node = map.get_map().get_node(path);
1524 1533 node.unwrap().unwrap().tracked_descendants_count()
1525 1534 }
1526 1535
1527 1536 /// Shortcut to return descendants with an entry.
1528 1537 /// Panics if the path does not exist.
1529 1538 fn descendants_with_an_entry(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1530 1539 let path = dbg!(HgPath::new(path));
1531 1540 let node = map.get_map().get_node(path);
1532 1541 node.unwrap().unwrap().descendants_with_entry_count()
1533 1542 }
1534 1543
1535 1544 fn assert_does_not_exist(map: &OwningDirstateMap, path: &[u8]) {
1536 1545 let path = dbg!(HgPath::new(path));
1537 1546 let node = map.get_map().get_node(path);
1538 1547 assert!(node.unwrap().is_none());
1539 1548 }
1540 1549
1541 1550 /// Shortcut for path creation in tests
1542 1551 fn p(b: &[u8]) -> &HgPath {
1543 1552 HgPath::new(b)
1544 1553 }
1545 1554
1546 1555 /// Test the very simple case a single tracked file
1547 1556 #[test]
1548 1557 fn test_tracked_descendants_simple() -> Result<(), DirstateError> {
1549 1558 let mut map = OwningDirstateMap::new_empty(vec![]);
1550 1559 assert_eq!(map.len(), 0);
1551 1560
1552 1561 map.set_tracked(p(b"some/nested/path"))?;
1553 1562
1554 1563 assert_eq!(map.len(), 1);
1555 1564 assert_eq!(tracked_descendants(&map, b"some"), 1);
1556 1565 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1557 1566 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1558 1567
1559 1568 map.set_untracked(p(b"some/nested/path"))?;
1560 1569 assert_eq!(map.len(), 0);
1561 1570 assert!(map.get_map().get_node(p(b"some"))?.is_none());
1562 1571
1563 1572 Ok(())
1564 1573 }
1565 1574
1566 1575 /// Test the simple case of all tracked, but multiple files
1567 1576 #[test]
1568 1577 fn test_tracked_descendants_multiple() -> Result<(), DirstateError> {
1569 1578 let mut map = OwningDirstateMap::new_empty(vec![]);
1570 1579
1571 1580 map.set_tracked(p(b"some/nested/path"))?;
1572 1581 map.set_tracked(p(b"some/nested/file"))?;
1573 1582 // one layer without any files to test deletion cascade
1574 1583 map.set_tracked(p(b"some/other/nested/path"))?;
1575 1584 map.set_tracked(p(b"root_file"))?;
1576 1585 map.set_tracked(p(b"some/file"))?;
1577 1586 map.set_tracked(p(b"some/file2"))?;
1578 1587 map.set_tracked(p(b"some/file3"))?;
1579 1588
1580 1589 assert_eq!(map.len(), 7);
1581 1590 assert_eq!(tracked_descendants(&map, b"some"), 6);
1582 1591 assert_eq!(tracked_descendants(&map, b"some/nested"), 2);
1583 1592 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1584 1593 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1585 1594 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1586 1595
1587 1596 map.set_untracked(p(b"some/nested/path"))?;
1588 1597 assert_eq!(map.len(), 6);
1589 1598 assert_eq!(tracked_descendants(&map, b"some"), 5);
1590 1599 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1591 1600 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1592 1601 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1593 1602
1594 1603 map.set_untracked(p(b"some/nested/file"))?;
1595 1604 assert_eq!(map.len(), 5);
1596 1605 assert_eq!(tracked_descendants(&map, b"some"), 4);
1597 1606 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1598 1607 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1599 1608 assert_does_not_exist(&map, b"some_nested");
1600 1609
1601 1610 map.set_untracked(p(b"some/other/nested/path"))?;
1602 1611 assert_eq!(map.len(), 4);
1603 1612 assert_eq!(tracked_descendants(&map, b"some"), 3);
1604 1613 assert_does_not_exist(&map, b"some/other");
1605 1614
1606 1615 map.set_untracked(p(b"root_file"))?;
1607 1616 assert_eq!(map.len(), 3);
1608 1617 assert_eq!(tracked_descendants(&map, b"some"), 3);
1609 1618 assert_does_not_exist(&map, b"root_file");
1610 1619
1611 1620 map.set_untracked(p(b"some/file"))?;
1612 1621 assert_eq!(map.len(), 2);
1613 1622 assert_eq!(tracked_descendants(&map, b"some"), 2);
1614 1623 assert_does_not_exist(&map, b"some/file");
1615 1624
1616 1625 map.set_untracked(p(b"some/file2"))?;
1617 1626 assert_eq!(map.len(), 1);
1618 1627 assert_eq!(tracked_descendants(&map, b"some"), 1);
1619 1628 assert_does_not_exist(&map, b"some/file2");
1620 1629
1621 1630 map.set_untracked(p(b"some/file3"))?;
1622 1631 assert_eq!(map.len(), 0);
1623 1632 assert_does_not_exist(&map, b"some/file3");
1624 1633
1625 1634 Ok(())
1626 1635 }
1627 1636
1628 1637 /// Check with a mix of tracked and non-tracked items
1629 1638 #[test]
1630 1639 fn test_tracked_descendants_different() -> Result<(), DirstateError> {
1631 1640 let mut map = OwningDirstateMap::new_empty(vec![]);
1632 1641
1633 1642 // A file that was just added
1634 1643 map.set_tracked(p(b"some/nested/path"))?;
1635 1644 // This has no information, the dirstate should ignore it
1636 1645 map.reset_state(p(b"some/file"), false, false, false, false, None)?;
1637 1646 assert_does_not_exist(&map, b"some/file");
1638 1647
1639 1648 // A file that was removed
1640 1649 map.reset_state(
1641 1650 p(b"some/nested/file"),
1642 1651 false,
1643 1652 true,
1644 1653 false,
1645 1654 false,
1646 1655 None,
1647 1656 )?;
1648 1657 assert!(!map.get(p(b"some/nested/file"))?.unwrap().tracked());
1649 1658 // Only present in p2
1650 1659 map.reset_state(p(b"some/file3"), false, false, true, false, None)?;
1651 1660 assert!(!map.get(p(b"some/file3"))?.unwrap().tracked());
1652 1661 // A file that was merged
1653 1662 map.reset_state(p(b"root_file"), true, true, true, false, None)?;
1654 1663 assert!(map.get(p(b"root_file"))?.unwrap().tracked());
1655 1664 // A file that is added, with info from p2
1656 1665 // XXX is that actually possible?
1657 1666 map.reset_state(p(b"some/file2"), true, false, true, false, None)?;
1658 1667 assert!(map.get(p(b"some/file2"))?.unwrap().tracked());
1659 1668 // A clean file
1660 1669 // One layer without any files to test deletion cascade
1661 1670 map.reset_state(
1662 1671 p(b"some/other/nested/path"),
1663 1672 true,
1664 1673 true,
1665 1674 false,
1666 1675 false,
1667 1676 None,
1668 1677 )?;
1669 1678 assert!(map.get(p(b"some/other/nested/path"))?.unwrap().tracked());
1670 1679
1671 1680 assert_eq!(map.len(), 6);
1672 1681 assert_eq!(tracked_descendants(&map, b"some"), 3);
1673 1682 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1674 1683 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1675 1684 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1676 1685 assert_eq!(tracked_descendants(&map, b"some/other/nested/path"), 0);
1677 1686 assert_eq!(
1678 1687 descendants_with_an_entry(&map, b"some/other/nested/path"),
1679 1688 0
1680 1689 );
1681 1690 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1682 1691 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1683 1692
1684 1693 // might as well check this
1685 1694 map.set_untracked(p(b"path/does/not/exist"))?;
1686 1695 assert_eq!(map.len(), 6);
1687 1696
1688 1697 map.set_untracked(p(b"some/other/nested/path"))?;
1689 1698 // It is set untracked but not deleted since it held other information
1690 1699 assert_eq!(map.len(), 6);
1691 1700 assert_eq!(tracked_descendants(&map, b"some"), 2);
1692 1701 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1693 1702 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1694 1703 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1695 1704 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1696 1705 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1697 1706
1698 1707 map.set_untracked(p(b"some/nested/path"))?;
1699 1708 // It is set untracked *and* deleted since it was only added
1700 1709 assert_eq!(map.len(), 5);
1701 1710 assert_eq!(tracked_descendants(&map, b"some"), 1);
1702 1711 assert_eq!(descendants_with_an_entry(&map, b"some"), 4);
1703 1712 assert_eq!(tracked_descendants(&map, b"some/nested"), 0);
1704 1713 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 1);
1705 1714 assert_does_not_exist(&map, b"some/nested/path");
1706 1715
1707 1716 map.set_untracked(p(b"root_file"))?;
1708 1717 // Untracked but not deleted
1709 1718 assert_eq!(map.len(), 5);
1710 1719 assert!(map.get(p(b"root_file"))?.is_some());
1711 1720
1712 1721 map.set_untracked(p(b"some/file2"))?;
1713 1722 assert_eq!(map.len(), 5);
1714 1723 assert_eq!(tracked_descendants(&map, b"some"), 0);
1715 1724 assert!(map.get(p(b"some/file2"))?.is_some());
1716 1725
1717 1726 map.set_untracked(p(b"some/file3"))?;
1718 1727 assert_eq!(map.len(), 5);
1719 1728 assert_eq!(tracked_descendants(&map, b"some"), 0);
1720 1729 assert!(map.get(p(b"some/file3"))?.is_some());
1721 1730
1722 1731 Ok(())
1723 1732 }
1724 1733
1725 1734 /// Check that copies counter is correctly updated
1726 1735 #[test]
1727 1736 fn test_copy_source() -> Result<(), DirstateError> {
1728 1737 let mut map = OwningDirstateMap::new_empty(vec![]);
1729 1738
1730 1739 // Clean file
1731 1740 map.reset_state(p(b"files/clean"), true, true, false, false, None)?;
1732 1741 // Merged file
1733 1742 map.reset_state(p(b"files/from_p2"), true, true, true, false, None)?;
1734 1743 // Removed file
1735 1744 map.reset_state(p(b"removed"), false, true, false, false, None)?;
1736 1745 // Added file
1737 1746 map.reset_state(p(b"files/added"), true, false, false, false, None)?;
1738 1747 // Add copy
1739 1748 map.copy_map_insert(p(b"files/clean"), p(b"clean_copy_source"))?;
1740 1749 assert_eq!(map.copy_map_len(), 1);
1741 1750
1742 1751 // Copy override
1743 1752 map.copy_map_insert(p(b"files/clean"), p(b"other_clean_copy_source"))?;
1744 1753 assert_eq!(map.copy_map_len(), 1);
1745 1754
1746 1755 // Multiple copies
1747 1756 map.copy_map_insert(p(b"removed"), p(b"removed_copy_source"))?;
1748 1757 assert_eq!(map.copy_map_len(), 2);
1749 1758
1750 1759 map.copy_map_insert(p(b"files/added"), p(b"added_copy_source"))?;
1751 1760 assert_eq!(map.copy_map_len(), 3);
1752 1761
1753 1762 // Added, so the entry is completely removed
1754 1763 map.set_untracked(p(b"files/added"))?;
1755 1764 assert_does_not_exist(&map, b"files/added");
1756 1765 assert_eq!(map.copy_map_len(), 2);
1757 1766
1758 1767 // Removed, so the entry is kept around, so is its copy
1759 1768 map.set_untracked(p(b"removed"))?;
1760 1769 assert!(map.get(p(b"removed"))?.is_some());
1761 1770 assert_eq!(map.copy_map_len(), 2);
1762 1771
1763 1772 // Clean, so the entry is kept around, but not its copy
1764 1773 map.set_untracked(p(b"files/clean"))?;
1765 1774 assert!(map.get(p(b"files/clean"))?.is_some());
1766 1775 assert_eq!(map.copy_map_len(), 1);
1767 1776
1768 1777 map.copy_map_insert(p(b"files/from_p2"), p(b"from_p2_copy_source"))?;
1769 1778 assert_eq!(map.copy_map_len(), 2);
1770 1779
1771 1780 // Info from p2, so its copy source info is kept around
1772 1781 map.set_untracked(p(b"files/from_p2"))?;
1773 1782 assert!(map.get(p(b"files/from_p2"))?.is_some());
1774 1783 assert_eq!(map.copy_map_len(), 2);
1775 1784
1776 1785 Ok(())
1777 1786 }
1778 1787
1779 1788 /// Test with "on disk" data. For the sake of this test, the "on disk" data
1780 1789 /// does not actually come from the disk, but it's opaque to the code being
1781 1790 /// tested.
1782 1791 #[test]
1783 1792 fn test_on_disk() -> Result<(), DirstateError> {
1784 1793 // First let's create some data to put "on disk"
1785 1794 let mut map = OwningDirstateMap::new_empty(vec![]);
1786 1795
1787 1796 // A file that was just added
1788 1797 map.set_tracked(p(b"some/nested/added"))?;
1789 1798 map.copy_map_insert(p(b"some/nested/added"), p(b"added_copy_source"))?;
1790 1799
1791 1800 // A file that was removed
1792 1801 map.reset_state(
1793 1802 p(b"some/nested/removed"),
1794 1803 false,
1795 1804 true,
1796 1805 false,
1797 1806 false,
1798 1807 None,
1799 1808 )?;
1800 1809 // Only present in p2
1801 1810 map.reset_state(
1802 1811 p(b"other/p2_info_only"),
1803 1812 false,
1804 1813 false,
1805 1814 true,
1806 1815 false,
1807 1816 None,
1808 1817 )?;
1809 1818 map.copy_map_insert(
1810 1819 p(b"other/p2_info_only"),
1811 1820 p(b"other/p2_info_copy_source"),
1812 1821 )?;
1813 1822 // A file that was merged
1814 1823 map.reset_state(p(b"merged"), true, true, true, false, None)?;
1815 1824 // A file that is added, with info from p2
1816 1825 // XXX is that actually possible?
1817 1826 map.reset_state(
1818 1827 p(b"other/added_with_p2"),
1819 1828 true,
1820 1829 false,
1821 1830 true,
1822 1831 false,
1823 1832 None,
1824 1833 )?;
1825 1834 // One layer without any files to test deletion cascade
1826 1835 // A clean file
1827 1836 map.reset_state(
1828 1837 p(b"some/other/nested/clean"),
1829 1838 true,
1830 1839 true,
1831 1840 false,
1832 1841 false,
1833 1842 None,
1834 1843 )?;
1835 1844
1836 1845 let (packed, metadata, _should_append, _old_data_size) =
1837 1846 map.pack_v2(DirstateMapWriteMode::ForceNewDataFile)?;
1838 1847 let packed_len = packed.len();
1839 1848 assert!(packed_len > 0);
1840 1849
1841 1850 // Recreate "from disk"
1842 1851 let mut map = OwningDirstateMap::new_v2(
1843 1852 packed,
1844 1853 packed_len,
1845 1854 metadata.as_bytes(),
1855 vec![],
1846 1856 )?;
1847 1857
1848 1858 // Check that everything is accounted for
1849 1859 assert!(map.contains_key(p(b"some/nested/added"))?);
1850 1860 assert!(map.contains_key(p(b"some/nested/removed"))?);
1851 1861 assert!(map.contains_key(p(b"merged"))?);
1852 1862 assert!(map.contains_key(p(b"other/p2_info_only"))?);
1853 1863 assert!(map.contains_key(p(b"other/added_with_p2"))?);
1854 1864 assert!(map.contains_key(p(b"some/other/nested/clean"))?);
1855 1865 assert_eq!(
1856 1866 map.copy_map_get(p(b"some/nested/added"))?,
1857 1867 Some(p(b"added_copy_source"))
1858 1868 );
1859 1869 assert_eq!(
1860 1870 map.copy_map_get(p(b"other/p2_info_only"))?,
1861 1871 Some(p(b"other/p2_info_copy_source"))
1862 1872 );
1863 1873 assert_eq!(tracked_descendants(&map, b"some"), 2);
1864 1874 assert_eq!(descendants_with_an_entry(&map, b"some"), 3);
1865 1875 assert_eq!(tracked_descendants(&map, b"other"), 1);
1866 1876 assert_eq!(descendants_with_an_entry(&map, b"other"), 2);
1867 1877 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1868 1878 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1869 1879 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1870 1880 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1871 1881 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1872 1882 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1873 1883 assert_eq!(map.len(), 6);
1874 1884 assert_eq!(map.get_map().unreachable_bytes, 0);
1875 1885 assert_eq!(map.copy_map_len(), 2);
1876 1886
1877 1887 // Shouldn't change anything since it's already not tracked
1878 1888 map.set_untracked(p(b"some/nested/removed"))?;
1879 1889 assert_eq!(map.get_map().unreachable_bytes, 0);
1880 1890
1881 1891 match map.get_map().root {
1882 1892 ChildNodes::InMemory(_) => {
1883 1893 panic!("root should not have been mutated")
1884 1894 }
1885 1895 _ => (),
1886 1896 }
1887 1897 // We haven't mutated enough (nothing, actually), we should still be in
1888 1898 // the append strategy
1889 1899 assert!(map.get_map().write_should_append());
1890 1900
1891 1901 // But this mutates the structure, so there should be unreachable_bytes
1892 1902 assert!(map.set_untracked(p(b"some/nested/added"))?);
1893 1903 let unreachable_bytes = map.get_map().unreachable_bytes;
1894 1904 assert!(unreachable_bytes > 0);
1895 1905
1896 1906 match map.get_map().root {
1897 1907 ChildNodes::OnDisk(_) => panic!("root should have been mutated"),
1898 1908 _ => (),
1899 1909 }
1900 1910
1901 1911 // This should not mutate the structure either, since `root` has
1902 1912 // already been mutated along with its direct children.
1903 1913 map.set_untracked(p(b"merged"))?;
1904 1914 assert_eq!(map.get_map().unreachable_bytes, unreachable_bytes);
1905 1915
1906 1916 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1907 1917 NodeRef::InMemory(_, _) => {
1908 1918 panic!("'other/added_with_p2' should not have been mutated")
1909 1919 }
1910 1920 _ => (),
1911 1921 }
1912 1922 // But this should, since it's in a different path
1913 1923 // than `<root>some/nested/add`
1914 1924 map.set_untracked(p(b"other/added_with_p2"))?;
1915 1925 assert!(map.get_map().unreachable_bytes > unreachable_bytes);
1916 1926
1917 1927 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1918 1928 NodeRef::OnDisk(_) => {
1919 1929 panic!("'other/added_with_p2' should have been mutated")
1920 1930 }
1921 1931 _ => (),
1922 1932 }
1923 1933
1924 1934 // We have rewritten most of the tree, we should create a new file
1925 1935 assert!(!map.get_map().write_should_append());
1926 1936
1927 1937 Ok(())
1928 1938 }
1929 1939 }
@@ -1,890 +1,892 b''
1 1 //! The "version 2" disk representation of the dirstate
2 2 //!
3 3 //! See `mercurial/helptext/internals/dirstate-v2.txt`
4 4
5 5 use crate::dirstate::{DirstateV2Data, TruncatedTimestamp};
6 6 use crate::dirstate_tree::dirstate_map::DirstateVersion;
7 7 use crate::dirstate_tree::dirstate_map::{
8 8 self, DirstateMap, DirstateMapWriteMode, NodeRef,
9 9 };
10 10 use crate::dirstate_tree::path_with_basename::WithBasename;
11 11 use crate::errors::HgError;
12 12 use crate::utils::hg_path::HgPath;
13 13 use crate::DirstateEntry;
14 14 use crate::DirstateError;
15 15 use crate::DirstateParents;
16 16 use bitflags::bitflags;
17 17 use bytes_cast::unaligned::{U16Be, U32Be};
18 18 use bytes_cast::BytesCast;
19 19 use format_bytes::format_bytes;
20 20 use rand::Rng;
21 21 use std::borrow::Cow;
22 22 use std::convert::{TryFrom, TryInto};
23 23 use std::fmt::Write;
24 24
25 25 /// Added at the start of `.hg/dirstate` when the "v2" format is used.
26 26 /// This a redundant sanity check more than an actual "magic number" since
27 27 /// `.hg/requires` already governs which format should be used.
28 28 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
29 29
30 30 /// Keep space for 256-bit hashes
31 31 const STORED_NODE_ID_BYTES: usize = 32;
32 32
33 33 /// … even though only 160 bits are used for now, with SHA-1
34 34 const USED_NODE_ID_BYTES: usize = 20;
35 35
36 36 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
37 37 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
38 38
39 39 /// Must match constants of the same names in `mercurial/dirstateutils/v2.py`
40 40 const TREE_METADATA_SIZE: usize = 44;
41 41 const NODE_SIZE: usize = 44;
42 42
43 43 /// Make sure that size-affecting changes are made knowingly
44 44 #[allow(unused)]
45 45 fn static_assert_size_of() {
46 46 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
47 47 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
48 48 let _ = std::mem::transmute::<Node, [u8; NODE_SIZE]>;
49 49 }
50 50
51 51 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
52 52 #[derive(BytesCast)]
53 53 #[repr(C)]
54 54 struct DocketHeader {
55 55 marker: [u8; V2_FORMAT_MARKER.len()],
56 56 parent_1: [u8; STORED_NODE_ID_BYTES],
57 57 parent_2: [u8; STORED_NODE_ID_BYTES],
58 58
59 59 metadata: TreeMetadata,
60 60
61 61 /// Counted in bytes
62 62 data_size: Size,
63 63
64 64 uuid_size: u8,
65 65 }
66 66
67 67 pub struct Docket<'on_disk> {
68 68 header: &'on_disk DocketHeader,
69 69 pub uuid: &'on_disk [u8],
70 70 }
71 71
72 72 /// Fields are documented in the *Tree metadata in the docket file*
73 73 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
74 74 #[derive(BytesCast)]
75 75 #[repr(C)]
76 76 pub struct TreeMetadata {
77 77 root_nodes: ChildNodes,
78 78 nodes_with_entry_count: Size,
79 79 nodes_with_copy_source_count: Size,
80 80 unreachable_bytes: Size,
81 81 unused: [u8; 4],
82 82
83 83 /// See *Optional hash of ignore patterns* section of
84 84 /// `mercurial/helptext/internals/dirstate-v2.txt`
85 85 ignore_patterns_hash: IgnorePatternsHash,
86 86 }
87 87
88 88 /// Fields are documented in the *The data file format*
89 89 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
90 90 #[derive(BytesCast, Debug)]
91 91 #[repr(C)]
92 92 pub(super) struct Node {
93 93 full_path: PathSlice,
94 94
95 95 /// In bytes from `self.full_path.start`
96 96 base_name_start: PathSize,
97 97
98 98 copy_source: OptPathSlice,
99 99 children: ChildNodes,
100 100 pub(super) descendants_with_entry_count: Size,
101 101 pub(super) tracked_descendants_count: Size,
102 102 flags: U16Be,
103 103 size: U32Be,
104 104 mtime: PackedTruncatedTimestamp,
105 105 }
106 106
107 107 bitflags! {
108 108 #[repr(C)]
109 109 struct Flags: u16 {
110 110 const WDIR_TRACKED = 1 << 0;
111 111 const P1_TRACKED = 1 << 1;
112 112 const P2_INFO = 1 << 2;
113 113 const MODE_EXEC_PERM = 1 << 3;
114 114 const MODE_IS_SYMLINK = 1 << 4;
115 115 const HAS_FALLBACK_EXEC = 1 << 5;
116 116 const FALLBACK_EXEC = 1 << 6;
117 117 const HAS_FALLBACK_SYMLINK = 1 << 7;
118 118 const FALLBACK_SYMLINK = 1 << 8;
119 119 const EXPECTED_STATE_IS_MODIFIED = 1 << 9;
120 120 const HAS_MODE_AND_SIZE = 1 <<10;
121 121 const HAS_MTIME = 1 <<11;
122 122 const MTIME_SECOND_AMBIGUOUS = 1 << 12;
123 123 const DIRECTORY = 1 <<13;
124 124 const ALL_UNKNOWN_RECORDED = 1 <<14;
125 125 const ALL_IGNORED_RECORDED = 1 <<15;
126 126 }
127 127 }
128 128
129 129 /// Duration since the Unix epoch
130 130 #[derive(BytesCast, Copy, Clone, Debug)]
131 131 #[repr(C)]
132 132 struct PackedTruncatedTimestamp {
133 133 truncated_seconds: U32Be,
134 134 nanoseconds: U32Be,
135 135 }
136 136
137 137 /// Counted in bytes from the start of the file
138 138 ///
139 139 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
140 140 type Offset = U32Be;
141 141
142 142 /// Counted in number of items
143 143 ///
144 144 /// NOTE: we choose not to support counting more than 4 billion nodes anywhere.
145 145 type Size = U32Be;
146 146
147 147 /// Counted in bytes
148 148 ///
149 149 /// NOTE: we choose not to support file names/paths longer than 64 KiB.
150 150 type PathSize = U16Be;
151 151
152 152 /// A contiguous sequence of `len` times `Node`, representing the child nodes
153 153 /// of either some other node or of the repository root.
154 154 ///
155 155 /// Always sorted by ascending `full_path`, to allow binary search.
156 156 /// Since nodes with the same parent nodes also have the same parent path,
157 157 /// only the `base_name`s need to be compared during binary search.
158 158 #[derive(BytesCast, Copy, Clone, Debug)]
159 159 #[repr(C)]
160 160 struct ChildNodes {
161 161 start: Offset,
162 162 len: Size,
163 163 }
164 164
165 165 /// A `HgPath` of `len` bytes
166 166 #[derive(BytesCast, Copy, Clone, Debug)]
167 167 #[repr(C)]
168 168 struct PathSlice {
169 169 start: Offset,
170 170 len: PathSize,
171 171 }
172 172
173 173 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
174 174 type OptPathSlice = PathSlice;
175 175
176 176 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
177 177 ///
178 178 /// This should only happen if Mercurial is buggy or a repository is corrupted.
179 179 #[derive(Debug)]
180 180 pub struct DirstateV2ParseError {
181 181 message: String,
182 182 }
183 183
184 184 impl DirstateV2ParseError {
185 185 pub fn new<S: Into<String>>(message: S) -> Self {
186 186 Self {
187 187 message: message.into(),
188 188 }
189 189 }
190 190 }
191 191
192 192 impl From<DirstateV2ParseError> for HgError {
193 193 fn from(e: DirstateV2ParseError) -> Self {
194 194 HgError::corrupted(format!("dirstate-v2 parse error: {}", e.message))
195 195 }
196 196 }
197 197
198 198 impl From<DirstateV2ParseError> for crate::DirstateError {
199 199 fn from(error: DirstateV2ParseError) -> Self {
200 200 HgError::from(error).into()
201 201 }
202 202 }
203 203
204 204 impl TreeMetadata {
205 205 pub fn as_bytes(&self) -> &[u8] {
206 206 BytesCast::as_bytes(self)
207 207 }
208 208 }
209 209
210 210 impl<'on_disk> Docket<'on_disk> {
211 211 /// Generate the identifier for a new data file
212 212 ///
213 213 /// TODO: support the `HGTEST_UUIDFILE` environment variable.
214 214 /// See `mercurial/revlogutils/docket.py`
215 215 pub fn new_uid() -> String {
216 216 const ID_LENGTH: usize = 8;
217 217 let mut id = String::with_capacity(ID_LENGTH);
218 218 let mut rng = rand::thread_rng();
219 219 for _ in 0..ID_LENGTH {
220 220 // One random hexadecimal digit.
221 221 // `unwrap` never panics because `impl Write for String`
222 222 // never returns an error.
223 223 write!(&mut id, "{:x}", rng.gen_range(0..16)).unwrap();
224 224 }
225 225 id
226 226 }
227 227
228 228 pub fn serialize(
229 229 parents: DirstateParents,
230 230 tree_metadata: TreeMetadata,
231 231 data_size: u64,
232 232 uuid: &[u8],
233 233 ) -> Result<Vec<u8>, std::num::TryFromIntError> {
234 234 let header = DocketHeader {
235 235 marker: *V2_FORMAT_MARKER,
236 236 parent_1: parents.p1.pad_to_256_bits(),
237 237 parent_2: parents.p2.pad_to_256_bits(),
238 238 metadata: tree_metadata,
239 239 data_size: u32::try_from(data_size)?.into(),
240 240 uuid_size: uuid.len().try_into()?,
241 241 };
242 242 let header = header.as_bytes();
243 243 let mut docket = Vec::with_capacity(header.len() + uuid.len());
244 244 docket.extend_from_slice(header);
245 245 docket.extend_from_slice(uuid);
246 246 Ok(docket)
247 247 }
248 248
249 249 pub fn parents(&self) -> DirstateParents {
250 250 use crate::Node;
251 251 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
252 252 .unwrap()
253 253 .clone();
254 254 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
255 255 .unwrap()
256 256 .clone();
257 257 DirstateParents { p1, p2 }
258 258 }
259 259
260 260 pub fn tree_metadata(&self) -> &[u8] {
261 261 self.header.metadata.as_bytes()
262 262 }
263 263
264 264 pub fn data_size(&self) -> usize {
265 265 // This `unwrap` could only panic on a 16-bit CPU
266 266 self.header.data_size.get().try_into().unwrap()
267 267 }
268 268
269 269 pub fn data_filename(&self) -> String {
270 270 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
271 271 }
272 272 }
273 273
274 274 pub fn read_docket(
275 275 on_disk: &[u8],
276 276 ) -> Result<Docket<'_>, DirstateV2ParseError> {
277 277 let (header, uuid) = DocketHeader::from_bytes(on_disk).map_err(|e| {
278 278 DirstateV2ParseError::new(format!("when reading docket, {}", e))
279 279 })?;
280 280 let uuid_size = header.uuid_size as usize;
281 281 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
282 282 Ok(Docket { header, uuid })
283 283 } else {
284 284 Err(DirstateV2ParseError::new(
285 285 "invalid format marker or uuid size",
286 286 ))
287 287 }
288 288 }
289 289
290 290 pub(super) fn read<'on_disk>(
291 291 on_disk: &'on_disk [u8],
292 292 metadata: &[u8],
293 uuid: Vec<u8>,
293 294 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
294 295 if on_disk.is_empty() {
295 296 let mut map = DirstateMap::empty(on_disk);
296 297 map.dirstate_version = DirstateVersion::V2;
297 298 return Ok(map);
298 299 }
299 300 let (meta, _) = TreeMetadata::from_bytes(metadata).map_err(|e| {
300 301 DirstateV2ParseError::new(format!("when parsing tree metadata, {}", e))
301 302 })?;
302 303 let dirstate_map = DirstateMap {
303 304 on_disk,
304 305 root: dirstate_map::ChildNodes::OnDisk(
305 306 read_nodes(on_disk, meta.root_nodes).map_err(|mut e| {
306 307 e.message = format!("{}, when reading root notes", e.message);
307 308 e
308 309 })?,
309 310 ),
310 311 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
311 312 nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(),
312 313 ignore_patterns_hash: meta.ignore_patterns_hash,
313 314 unreachable_bytes: meta.unreachable_bytes.get(),
314 315 old_data_size: on_disk.len(),
316 old_uuid: Some(uuid),
315 317 dirstate_version: DirstateVersion::V2,
316 318 write_mode: DirstateMapWriteMode::Auto,
317 319 };
318 320 Ok(dirstate_map)
319 321 }
320 322
321 323 impl Node {
322 324 pub(super) fn full_path<'on_disk>(
323 325 &self,
324 326 on_disk: &'on_disk [u8],
325 327 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
326 328 read_hg_path(on_disk, self.full_path)
327 329 }
328 330
329 331 pub(super) fn base_name_start<'on_disk>(
330 332 &self,
331 333 ) -> Result<usize, DirstateV2ParseError> {
332 334 let start = self.base_name_start.get();
333 335 if start < self.full_path.len.get() {
334 336 let start = usize::try_from(start)
335 337 // u32 -> usize, could only panic on a 16-bit CPU
336 338 .expect("dirstate-v2 base_name_start out of bounds");
337 339 Ok(start)
338 340 } else {
339 341 Err(DirstateV2ParseError::new("not enough bytes for base name"))
340 342 }
341 343 }
342 344
343 345 pub(super) fn base_name<'on_disk>(
344 346 &self,
345 347 on_disk: &'on_disk [u8],
346 348 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
347 349 let full_path = self.full_path(on_disk)?;
348 350 let base_name_start = self.base_name_start()?;
349 351 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
350 352 }
351 353
352 354 pub(super) fn path<'on_disk>(
353 355 &self,
354 356 on_disk: &'on_disk [u8],
355 357 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
356 358 Ok(WithBasename::from_raw_parts(
357 359 Cow::Borrowed(self.full_path(on_disk)?),
358 360 self.base_name_start()?,
359 361 ))
360 362 }
361 363
362 364 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
363 365 self.copy_source.start.get() != 0
364 366 }
365 367
366 368 pub(super) fn copy_source<'on_disk>(
367 369 &self,
368 370 on_disk: &'on_disk [u8],
369 371 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
370 372 Ok(if self.has_copy_source() {
371 373 Some(read_hg_path(on_disk, self.copy_source)?)
372 374 } else {
373 375 None
374 376 })
375 377 }
376 378
377 379 fn flags(&self) -> Flags {
378 380 Flags::from_bits_truncate(self.flags.get())
379 381 }
380 382
381 383 fn has_entry(&self) -> bool {
382 384 self.flags().intersects(
383 385 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
384 386 )
385 387 }
386 388
387 389 pub(super) fn node_data(
388 390 &self,
389 391 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
390 392 if self.has_entry() {
391 393 Ok(dirstate_map::NodeData::Entry(self.assume_entry()?))
392 394 } else if let Some(mtime) = self.cached_directory_mtime()? {
393 395 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
394 396 } else {
395 397 Ok(dirstate_map::NodeData::None)
396 398 }
397 399 }
398 400
399 401 pub(super) fn cached_directory_mtime(
400 402 &self,
401 403 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
402 404 // For now we do not have code to handle the absence of
403 405 // ALL_UNKNOWN_RECORDED, so we ignore the mtime if the flag is
404 406 // unset.
405 407 if self.flags().contains(Flags::DIRECTORY)
406 408 && self.flags().contains(Flags::HAS_MTIME)
407 409 && self.flags().contains(Flags::ALL_UNKNOWN_RECORDED)
408 410 {
409 411 Ok(Some(self.mtime()?))
410 412 } else {
411 413 Ok(None)
412 414 }
413 415 }
414 416
415 417 fn synthesize_unix_mode(&self) -> u32 {
416 418 let file_type = if self.flags().contains(Flags::MODE_IS_SYMLINK) {
417 419 libc::S_IFLNK
418 420 } else {
419 421 libc::S_IFREG
420 422 };
421 423 let permisions = if self.flags().contains(Flags::MODE_EXEC_PERM) {
422 424 0o755
423 425 } else {
424 426 0o644
425 427 };
426 428 (file_type | permisions).into()
427 429 }
428 430
429 431 fn mtime(&self) -> Result<TruncatedTimestamp, DirstateV2ParseError> {
430 432 let mut m: TruncatedTimestamp = self.mtime.try_into()?;
431 433 if self.flags().contains(Flags::MTIME_SECOND_AMBIGUOUS) {
432 434 m.second_ambiguous = true;
433 435 }
434 436 Ok(m)
435 437 }
436 438
437 439 fn assume_entry(&self) -> Result<DirstateEntry, DirstateV2ParseError> {
438 440 // TODO: convert through raw bits instead?
439 441 let wc_tracked = self.flags().contains(Flags::WDIR_TRACKED);
440 442 let p1_tracked = self.flags().contains(Flags::P1_TRACKED);
441 443 let p2_info = self.flags().contains(Flags::P2_INFO);
442 444 let mode_size = if self.flags().contains(Flags::HAS_MODE_AND_SIZE)
443 445 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
444 446 {
445 447 Some((self.synthesize_unix_mode(), self.size.into()))
446 448 } else {
447 449 None
448 450 };
449 451 let mtime = if self.flags().contains(Flags::HAS_MTIME)
450 452 && !self.flags().contains(Flags::DIRECTORY)
451 453 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
452 454 {
453 455 Some(self.mtime()?)
454 456 } else {
455 457 None
456 458 };
457 459 let fallback_exec = if self.flags().contains(Flags::HAS_FALLBACK_EXEC)
458 460 {
459 461 Some(self.flags().contains(Flags::FALLBACK_EXEC))
460 462 } else {
461 463 None
462 464 };
463 465 let fallback_symlink =
464 466 if self.flags().contains(Flags::HAS_FALLBACK_SYMLINK) {
465 467 Some(self.flags().contains(Flags::FALLBACK_SYMLINK))
466 468 } else {
467 469 None
468 470 };
469 471 Ok(DirstateEntry::from_v2_data(DirstateV2Data {
470 472 wc_tracked,
471 473 p1_tracked,
472 474 p2_info,
473 475 mode_size,
474 476 mtime,
475 477 fallback_exec,
476 478 fallback_symlink,
477 479 }))
478 480 }
479 481
480 482 pub(super) fn entry(
481 483 &self,
482 484 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
483 485 if self.has_entry() {
484 486 Ok(Some(self.assume_entry()?))
485 487 } else {
486 488 Ok(None)
487 489 }
488 490 }
489 491
490 492 pub(super) fn children<'on_disk>(
491 493 &self,
492 494 on_disk: &'on_disk [u8],
493 495 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
494 496 read_nodes(on_disk, self.children)
495 497 }
496 498
497 499 pub(super) fn to_in_memory_node<'on_disk>(
498 500 &self,
499 501 on_disk: &'on_disk [u8],
500 502 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
501 503 Ok(dirstate_map::Node {
502 504 children: dirstate_map::ChildNodes::OnDisk(
503 505 self.children(on_disk)?,
504 506 ),
505 507 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
506 508 data: self.node_data()?,
507 509 descendants_with_entry_count: self
508 510 .descendants_with_entry_count
509 511 .get(),
510 512 tracked_descendants_count: self.tracked_descendants_count.get(),
511 513 })
512 514 }
513 515
514 516 fn from_dirstate_entry(
515 517 entry: &DirstateEntry,
516 518 ) -> (Flags, U32Be, PackedTruncatedTimestamp) {
517 519 let DirstateV2Data {
518 520 wc_tracked,
519 521 p1_tracked,
520 522 p2_info,
521 523 mode_size: mode_size_opt,
522 524 mtime: mtime_opt,
523 525 fallback_exec,
524 526 fallback_symlink,
525 527 } = entry.v2_data();
526 528 // TODO: convert through raw flag bits instead?
527 529 let mut flags = Flags::empty();
528 530 flags.set(Flags::WDIR_TRACKED, wc_tracked);
529 531 flags.set(Flags::P1_TRACKED, p1_tracked);
530 532 flags.set(Flags::P2_INFO, p2_info);
531 533 let size = if let Some((m, s)) = mode_size_opt {
532 534 let exec_perm = m & (libc::S_IXUSR as u32) != 0;
533 535 let is_symlink = m & (libc::S_IFMT as u32) == libc::S_IFLNK as u32;
534 536 flags.set(Flags::MODE_EXEC_PERM, exec_perm);
535 537 flags.set(Flags::MODE_IS_SYMLINK, is_symlink);
536 538 flags.insert(Flags::HAS_MODE_AND_SIZE);
537 539 s.into()
538 540 } else {
539 541 0.into()
540 542 };
541 543 let mtime = if let Some(m) = mtime_opt {
542 544 flags.insert(Flags::HAS_MTIME);
543 545 if m.second_ambiguous {
544 546 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS);
545 547 };
546 548 m.into()
547 549 } else {
548 550 PackedTruncatedTimestamp::null()
549 551 };
550 552 if let Some(f_exec) = fallback_exec {
551 553 flags.insert(Flags::HAS_FALLBACK_EXEC);
552 554 if f_exec {
553 555 flags.insert(Flags::FALLBACK_EXEC);
554 556 }
555 557 }
556 558 if let Some(f_symlink) = fallback_symlink {
557 559 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
558 560 if f_symlink {
559 561 flags.insert(Flags::FALLBACK_SYMLINK);
560 562 }
561 563 }
562 564 (flags, size, mtime)
563 565 }
564 566 }
565 567
566 568 fn read_hg_path(
567 569 on_disk: &[u8],
568 570 slice: PathSlice,
569 571 ) -> Result<&HgPath, DirstateV2ParseError> {
570 572 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
571 573 }
572 574
573 575 fn read_nodes(
574 576 on_disk: &[u8],
575 577 slice: ChildNodes,
576 578 ) -> Result<&[Node], DirstateV2ParseError> {
577 579 read_slice(on_disk, slice.start, slice.len.get())
578 580 }
579 581
580 582 fn read_slice<T, Len>(
581 583 on_disk: &[u8],
582 584 start: Offset,
583 585 len: Len,
584 586 ) -> Result<&[T], DirstateV2ParseError>
585 587 where
586 588 T: BytesCast,
587 589 Len: TryInto<usize>,
588 590 {
589 591 // Either `usize::MAX` would result in "out of bounds" error since a single
590 592 // `&[u8]` cannot occupy the entire addess space.
591 593 let start = start.get().try_into().unwrap_or(std::usize::MAX);
592 594 let len = len.try_into().unwrap_or(std::usize::MAX);
593 595 let bytes = match on_disk.get(start..) {
594 596 Some(bytes) => bytes,
595 597 None => {
596 598 return Err(DirstateV2ParseError::new(
597 599 "not enough bytes from disk",
598 600 ))
599 601 }
600 602 };
601 603 T::slice_from_bytes(bytes, len)
602 604 .map_err(|e| {
603 605 DirstateV2ParseError::new(format!("when reading a slice, {}", e))
604 606 })
605 607 .map(|(slice, _rest)| slice)
606 608 }
607 609
608 610 pub(crate) fn for_each_tracked_path<'on_disk>(
609 611 on_disk: &'on_disk [u8],
610 612 metadata: &[u8],
611 613 mut f: impl FnMut(&'on_disk HgPath),
612 614 ) -> Result<(), DirstateV2ParseError> {
613 615 let (meta, _) = TreeMetadata::from_bytes(metadata).map_err(|e| {
614 616 DirstateV2ParseError::new(format!("when parsing tree metadata, {}", e))
615 617 })?;
616 618 fn recur<'on_disk>(
617 619 on_disk: &'on_disk [u8],
618 620 nodes: ChildNodes,
619 621 f: &mut impl FnMut(&'on_disk HgPath),
620 622 ) -> Result<(), DirstateV2ParseError> {
621 623 for node in read_nodes(on_disk, nodes)? {
622 624 if let Some(entry) = node.entry()? {
623 625 if entry.tracked() {
624 626 f(node.full_path(on_disk)?)
625 627 }
626 628 }
627 629 recur(on_disk, node.children, f)?
628 630 }
629 631 Ok(())
630 632 }
631 633 recur(on_disk, meta.root_nodes, &mut f)
632 634 }
633 635
634 636 /// Returns new data and metadata, together with whether that data should be
635 637 /// appended to the existing data file whose content is at
636 638 /// `dirstate_map.on_disk` (true), instead of written to a new data file
637 639 /// (false), and the previous size of data on disk.
638 640 pub(super) fn write(
639 641 dirstate_map: &DirstateMap,
640 642 write_mode: DirstateMapWriteMode,
641 643 ) -> Result<(Vec<u8>, TreeMetadata, bool, usize), DirstateError> {
642 644 let append = match write_mode {
643 645 DirstateMapWriteMode::Auto => dirstate_map.write_should_append(),
644 646 DirstateMapWriteMode::ForceNewDataFile => false,
645 647 DirstateMapWriteMode::ForceAppend => true,
646 648 };
647 649 if append {
648 650 log::trace!("appending to the dirstate data file");
649 651 } else {
650 652 log::trace!("creating new dirstate data file");
651 653 }
652 654
653 655 // This ignores the space for paths, and for nodes without an entry.
654 656 // TODO: better estimate? Skip the `Vec` and write to a file directly?
655 657 let size_guess = std::mem::size_of::<Node>()
656 658 * dirstate_map.nodes_with_entry_count as usize;
657 659
658 660 let mut writer = Writer {
659 661 dirstate_map,
660 662 append,
661 663 out: Vec::with_capacity(size_guess),
662 664 };
663 665
664 666 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
665 667
666 668 let unreachable_bytes = if append {
667 669 dirstate_map.unreachable_bytes
668 670 } else {
669 671 0
670 672 };
671 673 let meta = TreeMetadata {
672 674 root_nodes,
673 675 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
674 676 nodes_with_copy_source_count: dirstate_map
675 677 .nodes_with_copy_source_count
676 678 .into(),
677 679 unreachable_bytes: unreachable_bytes.into(),
678 680 unused: [0; 4],
679 681 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
680 682 };
681 683 Ok((writer.out, meta, append, dirstate_map.old_data_size))
682 684 }
683 685
684 686 struct Writer<'dmap, 'on_disk> {
685 687 dirstate_map: &'dmap DirstateMap<'on_disk>,
686 688 append: bool,
687 689 out: Vec<u8>,
688 690 }
689 691
690 692 impl Writer<'_, '_> {
691 693 fn write_nodes(
692 694 &mut self,
693 695 nodes: dirstate_map::ChildNodesRef,
694 696 ) -> Result<ChildNodes, DirstateError> {
695 697 // Reuse already-written nodes if possible
696 698 if self.append {
697 699 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
698 700 let start = self.on_disk_offset_of(nodes_slice).expect(
699 701 "dirstate-v2 OnDisk nodes not found within on_disk",
700 702 );
701 703 let len = child_nodes_len_from_usize(nodes_slice.len());
702 704 return Ok(ChildNodes { start, len });
703 705 }
704 706 }
705 707
706 708 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
707 709 // undefined iteration order. Sort to enable binary search in the
708 710 // written file.
709 711 let nodes = nodes.sorted();
710 712 let nodes_len = nodes.len();
711 713
712 714 // First accumulate serialized nodes in a `Vec`
713 715 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
714 716 for node in nodes {
715 717 let children =
716 718 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
717 719 let full_path = node.full_path(self.dirstate_map.on_disk)?;
718 720 let full_path = self.write_path(full_path.as_bytes());
719 721 let copy_source = if let Some(source) =
720 722 node.copy_source(self.dirstate_map.on_disk)?
721 723 {
722 724 self.write_path(source.as_bytes())
723 725 } else {
724 726 PathSlice {
725 727 start: 0.into(),
726 728 len: 0.into(),
727 729 }
728 730 };
729 731 on_disk_nodes.push(match node {
730 732 NodeRef::InMemory(path, node) => {
731 733 let (flags, size, mtime) = match &node.data {
732 734 dirstate_map::NodeData::Entry(entry) => {
733 735 Node::from_dirstate_entry(entry)
734 736 }
735 737 dirstate_map::NodeData::CachedDirectory { mtime } => {
736 738 // we currently never set a mtime if unknown file
737 739 // are present.
738 740 // So if we have a mtime for a directory, we know
739 741 // they are no unknown
740 742 // files and we
741 743 // blindly set ALL_UNKNOWN_RECORDED.
742 744 //
743 745 // We never set ALL_IGNORED_RECORDED since we
744 746 // don't track that case
745 747 // currently.
746 748 let mut flags = Flags::DIRECTORY
747 749 | Flags::HAS_MTIME
748 750 | Flags::ALL_UNKNOWN_RECORDED;
749 751 if mtime.second_ambiguous {
750 752 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS)
751 753 }
752 754 (flags, 0.into(), (*mtime).into())
753 755 }
754 756 dirstate_map::NodeData::None => (
755 757 Flags::DIRECTORY,
756 758 0.into(),
757 759 PackedTruncatedTimestamp::null(),
758 760 ),
759 761 };
760 762 Node {
761 763 children,
762 764 copy_source,
763 765 full_path,
764 766 base_name_start: u16::try_from(path.base_name_start())
765 767 // Could only panic for paths over 64 KiB
766 768 .expect("dirstate-v2 path length overflow")
767 769 .into(),
768 770 descendants_with_entry_count: node
769 771 .descendants_with_entry_count
770 772 .into(),
771 773 tracked_descendants_count: node
772 774 .tracked_descendants_count
773 775 .into(),
774 776 flags: flags.bits().into(),
775 777 size,
776 778 mtime,
777 779 }
778 780 }
779 781 NodeRef::OnDisk(node) => Node {
780 782 children,
781 783 copy_source,
782 784 full_path,
783 785 ..*node
784 786 },
785 787 })
786 788 }
787 789 // … so we can write them contiguously, after writing everything else
788 790 // they refer to.
789 791 let start = self.current_offset();
790 792 let len = child_nodes_len_from_usize(nodes_len);
791 793 self.out.extend(on_disk_nodes.as_bytes());
792 794 Ok(ChildNodes { start, len })
793 795 }
794 796
795 797 /// If the given slice of items is within `on_disk`, returns its offset
796 798 /// from the start of `on_disk`.
797 799 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
798 800 where
799 801 T: BytesCast,
800 802 {
801 803 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
802 804 let start = slice.as_ptr() as usize;
803 805 let end = start + slice.len();
804 806 start..=end
805 807 }
806 808 let slice_addresses = address_range(slice.as_bytes());
807 809 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
808 810 if on_disk_addresses.contains(slice_addresses.start())
809 811 && on_disk_addresses.contains(slice_addresses.end())
810 812 {
811 813 let offset = slice_addresses.start() - on_disk_addresses.start();
812 814 Some(offset_from_usize(offset))
813 815 } else {
814 816 None
815 817 }
816 818 }
817 819
818 820 fn current_offset(&mut self) -> Offset {
819 821 let mut offset = self.out.len();
820 822 if self.append {
821 823 offset += self.dirstate_map.on_disk.len()
822 824 }
823 825 offset_from_usize(offset)
824 826 }
825 827
826 828 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
827 829 let len = path_len_from_usize(slice.len());
828 830 // Reuse an already-written path if possible
829 831 if self.append {
830 832 if let Some(start) = self.on_disk_offset_of(slice) {
831 833 return PathSlice { start, len };
832 834 }
833 835 }
834 836 let start = self.current_offset();
835 837 self.out.extend(slice.as_bytes());
836 838 PathSlice { start, len }
837 839 }
838 840 }
839 841
840 842 fn offset_from_usize(x: usize) -> Offset {
841 843 u32::try_from(x)
842 844 // Could only panic for a dirstate file larger than 4 GiB
843 845 .expect("dirstate-v2 offset overflow")
844 846 .into()
845 847 }
846 848
847 849 fn child_nodes_len_from_usize(x: usize) -> Size {
848 850 u32::try_from(x)
849 851 // Could only panic with over 4 billion nodes
850 852 .expect("dirstate-v2 slice length overflow")
851 853 .into()
852 854 }
853 855
854 856 fn path_len_from_usize(x: usize) -> PathSize {
855 857 u16::try_from(x)
856 858 // Could only panic for paths over 64 KiB
857 859 .expect("dirstate-v2 path length overflow")
858 860 .into()
859 861 }
860 862
861 863 impl From<TruncatedTimestamp> for PackedTruncatedTimestamp {
862 864 fn from(timestamp: TruncatedTimestamp) -> Self {
863 865 Self {
864 866 truncated_seconds: timestamp.truncated_seconds().into(),
865 867 nanoseconds: timestamp.nanoseconds().into(),
866 868 }
867 869 }
868 870 }
869 871
870 872 impl TryFrom<PackedTruncatedTimestamp> for TruncatedTimestamp {
871 873 type Error = DirstateV2ParseError;
872 874
873 875 fn try_from(
874 876 timestamp: PackedTruncatedTimestamp,
875 877 ) -> Result<Self, Self::Error> {
876 878 Self::from_already_truncated(
877 879 timestamp.truncated_seconds.get(),
878 880 timestamp.nanoseconds.get(),
879 881 false,
880 882 )
881 883 }
882 884 }
883 885 impl PackedTruncatedTimestamp {
884 886 fn null() -> Self {
885 887 Self {
886 888 truncated_seconds: 0.into(),
887 889 nanoseconds: 0.into(),
888 890 }
889 891 }
890 892 }
@@ -1,89 +1,98 b''
1 1 use crate::{DirstateError, DirstateParents};
2 2
3 3 use super::dirstate_map::DirstateMap;
4 4 use std::ops::Deref;
5 5
6 6 use ouroboros::self_referencing;
7 7
8 8 /// Keep a `DirstateMap<'on_disk>` next to the `on_disk` buffer that it
9 9 /// borrows.
10 10 #[self_referencing]
11 11 pub struct OwningDirstateMap {
12 12 on_disk: Box<dyn Deref<Target = [u8]> + Send>,
13 13 #[borrows(on_disk)]
14 14 #[covariant]
15 15 map: DirstateMap<'this>,
16 16 }
17 17
18 18 impl OwningDirstateMap {
19 19 pub fn new_empty<OnDisk>(on_disk: OnDisk) -> Self
20 20 where
21 21 OnDisk: Deref<Target = [u8]> + Send + 'static,
22 22 {
23 23 let on_disk = Box::new(on_disk);
24 24
25 25 OwningDirstateMapBuilder {
26 26 on_disk,
27 27 map_builder: |bytes| DirstateMap::empty(&bytes),
28 28 }
29 29 .build()
30 30 }
31 31
32 32 pub fn new_v1<OnDisk>(
33 33 on_disk: OnDisk,
34 34 ) -> Result<(Self, DirstateParents), DirstateError>
35 35 where
36 36 OnDisk: Deref<Target = [u8]> + Send + 'static,
37 37 {
38 38 let on_disk = Box::new(on_disk);
39 39 let mut parents = DirstateParents::NULL;
40 40
41 41 Ok((
42 42 OwningDirstateMapTryBuilder {
43 43 on_disk,
44 44 map_builder: |bytes| {
45 45 DirstateMap::new_v1(&bytes).map(|(dmap, p)| {
46 46 parents = p.unwrap_or(DirstateParents::NULL);
47 47 dmap
48 48 })
49 49 },
50 50 }
51 51 .try_build()?,
52 52 parents,
53 53 ))
54 54 }
55 55
56 56 pub fn new_v2<OnDisk>(
57 57 on_disk: OnDisk,
58 58 data_size: usize,
59 59 metadata: &[u8],
60 uuid: Vec<u8>,
60 61 ) -> Result<Self, DirstateError>
61 62 where
62 63 OnDisk: Deref<Target = [u8]> + Send + 'static,
63 64 {
64 65 let on_disk = Box::new(on_disk);
65 66
66 67 OwningDirstateMapTryBuilder {
67 68 on_disk,
68 69 map_builder: |bytes| {
69 DirstateMap::new_v2(&bytes, data_size, metadata)
70 DirstateMap::new_v2(&bytes, data_size, metadata, uuid)
70 71 },
71 72 }
72 73 .try_build()
73 74 }
74 75
75 76 pub fn with_dmap_mut<R>(
76 77 &mut self,
77 78 f: impl FnOnce(&mut DirstateMap) -> R,
78 79 ) -> R {
79 80 self.with_map_mut(f)
80 81 }
81 82
82 83 pub fn get_map(&self) -> &DirstateMap {
83 84 self.borrow_map()
84 85 }
85 86
86 87 pub fn on_disk(&self) -> &[u8] {
87 88 self.borrow_on_disk()
88 89 }
90
91 pub fn old_uuid(&self) -> Option<&[u8]> {
92 self.get_map().old_uuid.as_deref()
93 }
94
95 pub fn old_data_size(&self) -> usize {
96 self.get_map().old_data_size
97 }
89 98 }
@@ -1,688 +1,689 b''
1 1 use crate::changelog::Changelog;
2 2 use crate::config::{Config, ConfigError, ConfigParseError};
3 3 use crate::dirstate::DirstateParents;
4 4 use crate::dirstate_tree::dirstate_map::DirstateMapWriteMode;
5 5 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
6 6 use crate::dirstate_tree::owning::OwningDirstateMap;
7 7 use crate::errors::HgResultExt;
8 8 use crate::errors::{HgError, IoResultExt};
9 9 use crate::lock::{try_with_lock_no_wait, LockError};
10 10 use crate::manifest::{Manifest, Manifestlog};
11 11 use crate::revlog::filelog::Filelog;
12 12 use crate::revlog::revlog::RevlogError;
13 13 use crate::utils::debug::debug_wait_for_file_or_print;
14 14 use crate::utils::files::get_path_from_bytes;
15 15 use crate::utils::hg_path::HgPath;
16 16 use crate::utils::SliceExt;
17 17 use crate::vfs::{is_dir, is_file, Vfs};
18 18 use crate::{requirements, NodePrefix};
19 19 use crate::{DirstateError, Revision};
20 20 use std::cell::{Ref, RefCell, RefMut};
21 21 use std::collections::HashSet;
22 22 use std::io::Seek;
23 23 use std::io::SeekFrom;
24 24 use std::io::Write as IoWrite;
25 25 use std::path::{Path, PathBuf};
26 26
27 27 const V2_MAX_READ_ATTEMPTS: usize = 5;
28 28
29 29 /// A repository on disk
30 30 pub struct Repo {
31 31 working_directory: PathBuf,
32 32 dot_hg: PathBuf,
33 33 store: PathBuf,
34 34 requirements: HashSet<String>,
35 35 config: Config,
36 36 dirstate_parents: LazyCell<DirstateParents>,
37 37 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>>,
38 38 dirstate_map: LazyCell<OwningDirstateMap>,
39 39 changelog: LazyCell<Changelog>,
40 40 manifestlog: LazyCell<Manifestlog>,
41 41 }
42 42
43 43 #[derive(Debug, derive_more::From)]
44 44 pub enum RepoError {
45 45 NotFound {
46 46 at: PathBuf,
47 47 },
48 48 #[from]
49 49 ConfigParseError(ConfigParseError),
50 50 #[from]
51 51 Other(HgError),
52 52 }
53 53
54 54 impl From<ConfigError> for RepoError {
55 55 fn from(error: ConfigError) -> Self {
56 56 match error {
57 57 ConfigError::Parse(error) => error.into(),
58 58 ConfigError::Other(error) => error.into(),
59 59 }
60 60 }
61 61 }
62 62
63 63 impl Repo {
64 64 /// tries to find nearest repository root in current working directory or
65 65 /// its ancestors
66 66 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
67 67 let current_directory = crate::utils::current_dir()?;
68 68 // ancestors() is inclusive: it first yields `current_directory`
69 69 // as-is.
70 70 for ancestor in current_directory.ancestors() {
71 71 if is_dir(ancestor.join(".hg"))? {
72 72 return Ok(ancestor.to_path_buf());
73 73 }
74 74 }
75 75 return Err(RepoError::NotFound {
76 76 at: current_directory,
77 77 });
78 78 }
79 79
80 80 /// Find a repository, either at the given path (which must contain a `.hg`
81 81 /// sub-directory) or by searching the current directory and its
82 82 /// ancestors.
83 83 ///
84 84 /// A method with two very different "modes" like this usually a code smell
85 85 /// to make two methods instead, but in this case an `Option` is what rhg
86 86 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
87 87 /// Having two methods would just move that `if` to almost all callers.
88 88 pub fn find(
89 89 config: &Config,
90 90 explicit_path: Option<PathBuf>,
91 91 ) -> Result<Self, RepoError> {
92 92 if let Some(root) = explicit_path {
93 93 if is_dir(root.join(".hg"))? {
94 94 Self::new_at_path(root.to_owned(), config)
95 95 } else if is_file(&root)? {
96 96 Err(HgError::unsupported("bundle repository").into())
97 97 } else {
98 98 Err(RepoError::NotFound {
99 99 at: root.to_owned(),
100 100 })
101 101 }
102 102 } else {
103 103 let root = Self::find_repo_root()?;
104 104 Self::new_at_path(root, config)
105 105 }
106 106 }
107 107
108 108 /// To be called after checking that `.hg` is a sub-directory
109 109 fn new_at_path(
110 110 working_directory: PathBuf,
111 111 config: &Config,
112 112 ) -> Result<Self, RepoError> {
113 113 let dot_hg = working_directory.join(".hg");
114 114
115 115 let mut repo_config_files = Vec::new();
116 116 repo_config_files.push(dot_hg.join("hgrc"));
117 117 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
118 118
119 119 let hg_vfs = Vfs { base: &dot_hg };
120 120 let mut reqs = requirements::load_if_exists(hg_vfs)?;
121 121 let relative =
122 122 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
123 123 let shared =
124 124 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
125 125
126 126 // From `mercurial/localrepo.py`:
127 127 //
128 128 // if .hg/requires contains the sharesafe requirement, it means
129 129 // there exists a `.hg/store/requires` too and we should read it
130 130 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
131 131 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
132 132 // is not present, refer checkrequirementscompat() for that
133 133 //
134 134 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
135 135 // repository was shared the old way. We check the share source
136 136 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
137 137 // current repository needs to be reshared
138 138 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
139 139
140 140 let store_path;
141 141 if !shared {
142 142 store_path = dot_hg.join("store");
143 143 } else {
144 144 let bytes = hg_vfs.read("sharedpath")?;
145 145 let mut shared_path =
146 146 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
147 147 .to_owned();
148 148 if relative {
149 149 shared_path = dot_hg.join(shared_path)
150 150 }
151 151 if !is_dir(&shared_path)? {
152 152 return Err(HgError::corrupted(format!(
153 153 ".hg/sharedpath points to nonexistent directory {}",
154 154 shared_path.display()
155 155 ))
156 156 .into());
157 157 }
158 158
159 159 store_path = shared_path.join("store");
160 160
161 161 let source_is_share_safe =
162 162 requirements::load(Vfs { base: &shared_path })?
163 163 .contains(requirements::SHARESAFE_REQUIREMENT);
164 164
165 165 if share_safe != source_is_share_safe {
166 166 return Err(HgError::unsupported("share-safe mismatch").into());
167 167 }
168 168
169 169 if share_safe {
170 170 repo_config_files.insert(0, shared_path.join("hgrc"))
171 171 }
172 172 }
173 173 if share_safe {
174 174 reqs.extend(requirements::load(Vfs { base: &store_path })?);
175 175 }
176 176
177 177 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
178 178 config.combine_with_repo(&repo_config_files)?
179 179 } else {
180 180 config.clone()
181 181 };
182 182
183 183 let repo = Self {
184 184 requirements: reqs,
185 185 working_directory,
186 186 store: store_path,
187 187 dot_hg,
188 188 config: repo_config,
189 189 dirstate_parents: LazyCell::new(),
190 190 dirstate_data_file_uuid: LazyCell::new(),
191 191 dirstate_map: LazyCell::new(),
192 192 changelog: LazyCell::new(),
193 193 manifestlog: LazyCell::new(),
194 194 };
195 195
196 196 requirements::check(&repo)?;
197 197
198 198 Ok(repo)
199 199 }
200 200
201 201 pub fn working_directory_path(&self) -> &Path {
202 202 &self.working_directory
203 203 }
204 204
205 205 pub fn requirements(&self) -> &HashSet<String> {
206 206 &self.requirements
207 207 }
208 208
209 209 pub fn config(&self) -> &Config {
210 210 &self.config
211 211 }
212 212
213 213 /// For accessing repository files (in `.hg`), except for the store
214 214 /// (`.hg/store`).
215 215 pub fn hg_vfs(&self) -> Vfs<'_> {
216 216 Vfs { base: &self.dot_hg }
217 217 }
218 218
219 219 /// For accessing repository store files (in `.hg/store`)
220 220 pub fn store_vfs(&self) -> Vfs<'_> {
221 221 Vfs { base: &self.store }
222 222 }
223 223
224 224 /// For accessing the working copy
225 225 pub fn working_directory_vfs(&self) -> Vfs<'_> {
226 226 Vfs {
227 227 base: &self.working_directory,
228 228 }
229 229 }
230 230
231 231 pub fn try_with_wlock_no_wait<R>(
232 232 &self,
233 233 f: impl FnOnce() -> R,
234 234 ) -> Result<R, LockError> {
235 235 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
236 236 }
237 237
238 238 pub fn has_dirstate_v2(&self) -> bool {
239 239 self.requirements
240 240 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
241 241 }
242 242
243 243 pub fn has_sparse(&self) -> bool {
244 244 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
245 245 }
246 246
247 247 pub fn has_narrow(&self) -> bool {
248 248 self.requirements.contains(requirements::NARROW_REQUIREMENT)
249 249 }
250 250
251 251 pub fn has_nodemap(&self) -> bool {
252 252 self.requirements
253 253 .contains(requirements::NODEMAP_REQUIREMENT)
254 254 }
255 255
256 256 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
257 257 Ok(self
258 258 .hg_vfs()
259 259 .read("dirstate")
260 260 .io_not_found_as_none()?
261 261 .unwrap_or(Vec::new()))
262 262 }
263 263
264 264 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
265 265 Ok(*self
266 266 .dirstate_parents
267 267 .get_or_init(|| self.read_dirstate_parents())?)
268 268 }
269 269
270 270 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
271 271 let dirstate = self.dirstate_file_contents()?;
272 272 let parents = if dirstate.is_empty() {
273 273 if self.has_dirstate_v2() {
274 274 self.dirstate_data_file_uuid.set(None);
275 275 }
276 276 DirstateParents::NULL
277 277 } else if self.has_dirstate_v2() {
278 278 let docket =
279 279 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
280 280 self.dirstate_data_file_uuid
281 281 .set(Some(docket.uuid.to_owned()));
282 282 docket.parents()
283 283 } else {
284 284 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
285 285 .clone()
286 286 };
287 287 self.dirstate_parents.set(parents);
288 288 Ok(parents)
289 289 }
290 290
291 291 fn read_dirstate_data_file_uuid(
292 292 &self,
293 293 ) -> Result<Option<Vec<u8>>, HgError> {
294 294 assert!(
295 295 self.has_dirstate_v2(),
296 296 "accessing dirstate data file ID without dirstate-v2"
297 297 );
298 298 let dirstate = self.dirstate_file_contents()?;
299 299 if dirstate.is_empty() {
300 300 self.dirstate_parents.set(DirstateParents::NULL);
301 301 Ok(None)
302 302 } else {
303 303 let docket =
304 304 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
305 305 self.dirstate_parents.set(docket.parents());
306 306 Ok(Some(docket.uuid.to_owned()))
307 307 }
308 308 }
309 309
310 310 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
311 311 if self.has_dirstate_v2() {
312 312 // The v2 dirstate is split into a docket and a data file.
313 313 // Since we don't always take the `wlock` to read it
314 314 // (like in `hg status`), it is susceptible to races.
315 315 // A simple retry method should be enough since full rewrites
316 316 // only happen when too much garbage data is present and
317 317 // this race is unlikely.
318 318 let mut tries = 0;
319 319
320 320 while tries < V2_MAX_READ_ATTEMPTS {
321 321 tries += 1;
322 322 match self.read_docket_and_data_file() {
323 323 Ok(m) => {
324 324 return Ok(m);
325 325 }
326 326 Err(e) => match e {
327 327 DirstateError::Common(HgError::RaceDetected(
328 328 context,
329 329 )) => {
330 330 log::info!(
331 331 "dirstate read race detected {} (retry {}/{})",
332 332 context,
333 333 tries,
334 334 V2_MAX_READ_ATTEMPTS,
335 335 );
336 336 continue;
337 337 }
338 338 _ => return Err(e.into()),
339 339 },
340 340 }
341 341 }
342 342 let error = HgError::abort(
343 343 format!("dirstate read race happened {tries} times in a row"),
344 344 255,
345 345 None,
346 346 );
347 347 return Err(DirstateError::Common(error));
348 348 } else {
349 349 debug_wait_for_file_or_print(
350 350 self.config(),
351 351 "dirstate.pre-read-file",
352 352 );
353 353 let dirstate_file_contents = self.dirstate_file_contents()?;
354 354 return if dirstate_file_contents.is_empty() {
355 355 self.dirstate_parents.set(DirstateParents::NULL);
356 356 Ok(OwningDirstateMap::new_empty(Vec::new()))
357 357 } else {
358 358 let (map, parents) =
359 359 OwningDirstateMap::new_v1(dirstate_file_contents)?;
360 360 self.dirstate_parents.set(parents);
361 361 Ok(map)
362 362 };
363 363 }
364 364 }
365 365
366 366 fn read_docket_and_data_file(
367 367 &self,
368 368 ) -> Result<OwningDirstateMap, DirstateError> {
369 369 debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
370 370 let dirstate_file_contents = self.dirstate_file_contents()?;
371 371 if dirstate_file_contents.is_empty() {
372 372 self.dirstate_parents.set(DirstateParents::NULL);
373 373 self.dirstate_data_file_uuid.set(None);
374 374 return Ok(OwningDirstateMap::new_empty(Vec::new()));
375 375 }
376 376 let docket = crate::dirstate_tree::on_disk::read_docket(
377 377 &dirstate_file_contents,
378 378 )?;
379 379 debug_wait_for_file_or_print(
380 380 self.config(),
381 381 "dirstate.post-docket-read-file",
382 382 );
383 383 self.dirstate_parents.set(docket.parents());
384 384 self.dirstate_data_file_uuid
385 385 .set(Some(docket.uuid.to_owned()));
386 let uuid = docket.uuid.to_owned();
386 387 let data_size = docket.data_size();
387 388
388 389 let context = "between reading dirstate docket and data file";
389 390 let race_error = HgError::RaceDetected(context.into());
390 391 let metadata = docket.tree_metadata();
391 392
392 393 let mut map = if crate::vfs::is_on_nfs_mount(docket.data_filename()) {
393 394 // Don't mmap on NFS to prevent `SIGBUS` error on deletion
394 395 let contents = self.hg_vfs().read(docket.data_filename());
395 396 let contents = match contents {
396 397 Ok(c) => c,
397 398 Err(HgError::IoError { error, context }) => {
398 399 match error.raw_os_error().expect("real os error") {
399 400 // 2 = ENOENT, No such file or directory
400 401 // 116 = ESTALE, Stale NFS file handle
401 402 //
402 403 // TODO match on `error.kind()` when
403 404 // `ErrorKind::StaleNetworkFileHandle` is stable.
404 405 2 | 116 => {
405 406 // Race where the data file was deleted right after
406 407 // we read the docket, try again
407 408 return Err(race_error.into());
408 409 }
409 410 _ => {
410 411 return Err(
411 412 HgError::IoError { error, context }.into()
412 413 )
413 414 }
414 415 }
415 416 }
416 417 Err(e) => return Err(e.into()),
417 418 };
418 OwningDirstateMap::new_v2(contents, data_size, metadata)
419 OwningDirstateMap::new_v2(contents, data_size, metadata, uuid)
419 420 } else {
420 421 match self
421 422 .hg_vfs()
422 423 .mmap_open(docket.data_filename())
423 424 .io_not_found_as_none()
424 425 {
425 Ok(Some(data_mmap)) => {
426 OwningDirstateMap::new_v2(data_mmap, data_size, metadata)
427 }
426 Ok(Some(data_mmap)) => OwningDirstateMap::new_v2(
427 data_mmap, data_size, metadata, uuid,
428 ),
428 429 Ok(None) => {
429 430 // Race where the data file was deleted right after we
430 431 // read the docket, try again
431 432 return Err(race_error.into());
432 433 }
433 434 Err(e) => return Err(e.into()),
434 435 }
435 436 }?;
436 437
437 438 let write_mode_config = self
438 439 .config()
439 440 .get_str(b"devel", b"dirstate.v2.data_update_mode")
440 441 .unwrap_or(Some("auto"))
441 442 .unwrap_or("auto"); // don't bother for devel options
442 443 let write_mode = match write_mode_config {
443 444 "auto" => DirstateMapWriteMode::Auto,
444 445 "force-new" => DirstateMapWriteMode::ForceNewDataFile,
445 446 "force-append" => DirstateMapWriteMode::ForceAppend,
446 447 _ => DirstateMapWriteMode::Auto,
447 448 };
448 449
449 450 map.with_dmap_mut(|m| m.set_write_mode(write_mode));
450 451
451 452 Ok(map)
452 453 }
453 454
454 455 pub fn dirstate_map(
455 456 &self,
456 457 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
457 458 self.dirstate_map.get_or_init(|| self.new_dirstate_map())
458 459 }
459 460
460 461 pub fn dirstate_map_mut(
461 462 &self,
462 463 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
463 464 self.dirstate_map
464 465 .get_mut_or_init(|| self.new_dirstate_map())
465 466 }
466 467
467 468 fn new_changelog(&self) -> Result<Changelog, HgError> {
468 469 Changelog::open(&self.store_vfs(), self.has_nodemap())
469 470 }
470 471
471 472 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
472 473 self.changelog.get_or_init(|| self.new_changelog())
473 474 }
474 475
475 476 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
476 477 self.changelog.get_mut_or_init(|| self.new_changelog())
477 478 }
478 479
479 480 fn new_manifestlog(&self) -> Result<Manifestlog, HgError> {
480 481 Manifestlog::open(&self.store_vfs(), self.has_nodemap())
481 482 }
482 483
483 484 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
484 485 self.manifestlog.get_or_init(|| self.new_manifestlog())
485 486 }
486 487
487 488 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
488 489 self.manifestlog.get_mut_or_init(|| self.new_manifestlog())
489 490 }
490 491
491 492 /// Returns the manifest of the *changeset* with the given node ID
492 493 pub fn manifest_for_node(
493 494 &self,
494 495 node: impl Into<NodePrefix>,
495 496 ) -> Result<Manifest, RevlogError> {
496 497 self.manifestlog()?.data_for_node(
497 498 self.changelog()?
498 499 .data_for_node(node.into())?
499 500 .manifest_node()?
500 501 .into(),
501 502 )
502 503 }
503 504
504 505 /// Returns the manifest of the *changeset* with the given revision number
505 506 pub fn manifest_for_rev(
506 507 &self,
507 508 revision: Revision,
508 509 ) -> Result<Manifest, RevlogError> {
509 510 self.manifestlog()?.data_for_node(
510 511 self.changelog()?
511 512 .data_for_rev(revision)?
512 513 .manifest_node()?
513 514 .into(),
514 515 )
515 516 }
516 517
517 518 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
518 519 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
519 520 Ok(entry.tracked())
520 521 } else {
521 522 Ok(false)
522 523 }
523 524 }
524 525
525 526 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
526 527 Filelog::open(self, path)
527 528 }
528 529
529 530 /// Write to disk any updates that were made through `dirstate_map_mut`.
530 531 ///
531 532 /// The "wlock" must be held while calling this.
532 533 /// See for example `try_with_wlock_no_wait`.
533 534 ///
534 535 /// TODO: have a `WritableRepo` type only accessible while holding the
535 536 /// lock?
536 537 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
537 538 let map = self.dirstate_map()?;
538 539 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
539 540 // it’s unset
540 541 let parents = self.dirstate_parents()?;
541 542 let (packed_dirstate, old_uuid_to_remove) = if self.has_dirstate_v2() {
542 543 let uuid_opt = self
543 544 .dirstate_data_file_uuid
544 545 .get_or_init(|| self.read_dirstate_data_file_uuid())?;
545 546 let uuid_opt = uuid_opt.as_ref();
546 547 let write_mode = if uuid_opt.is_some() {
547 548 DirstateMapWriteMode::Auto
548 549 } else {
549 550 DirstateMapWriteMode::ForceNewDataFile
550 551 };
551 552 let (data, tree_metadata, append, old_data_size) =
552 553 map.pack_v2(write_mode)?;
553 554
554 555 // Reuse the uuid, or generate a new one, keeping the old for
555 556 // deletion.
556 557 let (uuid, old_uuid) = match uuid_opt {
557 558 Some(uuid) => {
558 559 let as_str = std::str::from_utf8(uuid)
559 560 .map_err(|_| {
560 561 HgError::corrupted(
561 562 "non-UTF-8 dirstate data file ID",
562 563 )
563 564 })?
564 565 .to_owned();
565 566 if append {
566 567 (as_str, None)
567 568 } else {
568 569 (DirstateDocket::new_uid(), Some(as_str))
569 570 }
570 571 }
571 572 None => (DirstateDocket::new_uid(), None),
572 573 };
573 574
574 575 let data_filename = format!("dirstate.{}", uuid);
575 576 let data_filename = self.hg_vfs().join(data_filename);
576 577 let mut options = std::fs::OpenOptions::new();
577 578 options.write(true);
578 579
579 580 // Why are we not using the O_APPEND flag when appending?
580 581 //
581 582 // - O_APPEND makes it trickier to deal with garbage at the end of
582 583 // the file, left by a previous uncommitted transaction. By
583 584 // starting the write at [old_data_size] we make sure we erase
584 585 // all such garbage.
585 586 //
586 587 // - O_APPEND requires to special-case 0-byte writes, whereas we
587 588 // don't need that.
588 589 //
589 590 // - Some OSes have bugs in implementation O_APPEND:
590 591 // revlog.py talks about a Solaris bug, but we also saw some ZFS
591 592 // bug: https://github.com/openzfs/zfs/pull/3124,
592 593 // https://github.com/openzfs/zfs/issues/13370
593 594 //
594 595 if !append {
595 596 log::trace!("creating a new dirstate data file");
596 597 options.create_new(true);
597 598 } else {
598 599 log::trace!("appending to the dirstate data file");
599 600 }
600 601
601 602 let data_size = (|| {
602 603 // TODO: loop and try another random ID if !append and this
603 604 // returns `ErrorKind::AlreadyExists`? Collision chance of two
604 605 // random IDs is one in 2**32
605 606 let mut file = options.open(&data_filename)?;
606 607 if append {
607 608 file.seek(SeekFrom::Start(old_data_size as u64))?;
608 609 }
609 610 file.write_all(&data)?;
610 611 file.flush()?;
611 612 file.seek(SeekFrom::Current(0))
612 613 })()
613 614 .when_writing_file(&data_filename)?;
614 615
615 616 let packed_dirstate = DirstateDocket::serialize(
616 617 parents,
617 618 tree_metadata,
618 619 data_size,
619 620 uuid.as_bytes(),
620 621 )
621 622 .map_err(|_: std::num::TryFromIntError| {
622 623 HgError::corrupted("overflow in dirstate docket serialization")
623 624 })?;
624 625
625 626 (packed_dirstate, old_uuid)
626 627 } else {
627 628 (map.pack_v1(parents)?, None)
628 629 };
629 630
630 631 let vfs = self.hg_vfs();
631 632 vfs.atomic_write("dirstate", &packed_dirstate)?;
632 633 if let Some(uuid) = old_uuid_to_remove {
633 634 // Remove the old data file after the new docket pointing to the
634 635 // new data file was written.
635 636 vfs.remove_file(format!("dirstate.{}", uuid))?;
636 637 }
637 638 Ok(())
638 639 }
639 640 }
640 641
641 642 /// Lazily-initialized component of `Repo` with interior mutability
642 643 ///
643 644 /// This differs from `OnceCell` in that the value can still be "deinitialized"
644 645 /// later by setting its inner `Option` to `None`. It also takes the
645 646 /// initialization function as an argument when the value is requested, not
646 647 /// when the instance is created.
647 648 struct LazyCell<T> {
648 649 value: RefCell<Option<T>>,
649 650 }
650 651
651 652 impl<T> LazyCell<T> {
652 653 fn new() -> Self {
653 654 Self {
654 655 value: RefCell::new(None),
655 656 }
656 657 }
657 658
658 659 fn set(&self, value: T) {
659 660 *self.value.borrow_mut() = Some(value)
660 661 }
661 662
662 663 fn get_or_init<E>(
663 664 &self,
664 665 init: impl Fn() -> Result<T, E>,
665 666 ) -> Result<Ref<T>, E> {
666 667 let mut borrowed = self.value.borrow();
667 668 if borrowed.is_none() {
668 669 drop(borrowed);
669 670 // Only use `borrow_mut` if it is really needed to avoid panic in
670 671 // case there is another outstanding borrow but mutation is not
671 672 // needed.
672 673 *self.value.borrow_mut() = Some(init()?);
673 674 borrowed = self.value.borrow()
674 675 }
675 676 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
676 677 }
677 678
678 679 fn get_mut_or_init<E>(
679 680 &self,
680 681 init: impl Fn() -> Result<T, E>,
681 682 ) -> Result<RefMut<T>, E> {
682 683 let mut borrowed = self.value.borrow_mut();
683 684 if borrowed.is_none() {
684 685 *borrowed = Some(init()?);
685 686 }
686 687 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
687 688 }
688 689 }
@@ -1,551 +1,561 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, dirstate_tree::dirstate_map::DirstateMapWriteMode,
27 27 dirstate_tree::on_disk::DirstateV2ParseError,
28 28 dirstate_tree::owning::OwningDirstateMap, revlog::Node,
29 29 utils::files::normalize_case, utils::hg_path::HgPath, DirstateEntry,
30 30 DirstateError, DirstateParents,
31 31 };
32 32
33 33 // TODO
34 34 // This object needs to share references to multiple members of its Rust
35 35 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
36 36 // Right now `CopyMap` is done, but it needs to have an explicit reference
37 37 // to `RustDirstateMap` which itself needs to have an encapsulation for
38 38 // every method in `CopyMap` (copymapcopy, etc.).
39 39 // This is ugly and hard to maintain.
40 40 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
41 41 // `py_class!` is already implemented and does not mention
42 42 // `RustDirstateMap`, rightfully so.
43 43 // All attributes also have to have a separate refcount data attribute for
44 44 // leaks, with all methods that go along for reference sharing.
45 45 py_class!(pub class DirstateMap |py| {
46 46 @shared data inner: OwningDirstateMap;
47 47
48 48 /// Returns a `(dirstate_map, parents)` tuple
49 49 @staticmethod
50 50 def new_v1(
51 51 on_disk: PyBytes,
52 52 ) -> PyResult<PyObject> {
53 53 let on_disk = PyBytesDeref::new(py, on_disk);
54 54 let (map, parents) = OwningDirstateMap::new_v1(on_disk)
55 55 .map_err(|e| dirstate_error(py, e))?;
56 56 let map = Self::create_instance(py, map)?;
57 57 let p1 = PyBytes::new(py, parents.p1.as_bytes());
58 58 let p2 = PyBytes::new(py, parents.p2.as_bytes());
59 59 let parents = (p1, p2);
60 60 Ok((map, parents).to_py_object(py).into_object())
61 61 }
62 62
63 63 /// Returns a DirstateMap
64 64 @staticmethod
65 65 def new_v2(
66 66 on_disk: PyBytes,
67 67 data_size: usize,
68 68 tree_metadata: PyBytes,
69 uuid: PyBytes,
69 70 ) -> PyResult<PyObject> {
70 71 let dirstate_error = |e: DirstateError| {
71 72 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
72 73 };
73 74 let on_disk = PyBytesDeref::new(py, on_disk);
75 let uuid = uuid.data(py);
74 76 let map = OwningDirstateMap::new_v2(
75 on_disk, data_size, tree_metadata.data(py),
77 on_disk, data_size, tree_metadata.data(py), uuid.to_owned(),
76 78 ).map_err(dirstate_error)?;
77 79 let map = Self::create_instance(py, map)?;
78 80 Ok(map.into_object())
79 81 }
80 82
83 /// Returns an empty DirstateMap. Only used for a new dirstate.
84 @staticmethod
85 def new_empty() -> PyResult<PyObject> {
86 let map = OwningDirstateMap::new_empty(vec![]);
87 let map = Self::create_instance(py, map)?;
88 Ok(map.into_object())
89 }
90
81 91 def clear(&self) -> PyResult<PyObject> {
82 92 self.inner(py).borrow_mut().clear();
83 93 Ok(py.None())
84 94 }
85 95
86 96 def get(
87 97 &self,
88 98 key: PyObject,
89 99 default: Option<PyObject> = None
90 100 ) -> PyResult<Option<PyObject>> {
91 101 let key = key.extract::<PyBytes>(py)?;
92 102 match self
93 103 .inner(py)
94 104 .borrow()
95 105 .get(HgPath::new(key.data(py)))
96 106 .map_err(|e| v2_error(py, e))?
97 107 {
98 108 Some(entry) => {
99 109 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
100 110 },
101 111 None => Ok(default)
102 112 }
103 113 }
104 114
105 115 def set_tracked(&self, f: PyObject) -> PyResult<PyBool> {
106 116 let bytes = f.extract::<PyBytes>(py)?;
107 117 let path = HgPath::new(bytes.data(py));
108 118 let res = self.inner(py).borrow_mut().set_tracked(path);
109 119 let was_tracked = res.or_else(|_| {
110 120 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
111 121 })?;
112 122 Ok(was_tracked.to_py_object(py))
113 123 }
114 124
115 125 def set_untracked(&self, f: PyObject) -> PyResult<PyBool> {
116 126 let bytes = f.extract::<PyBytes>(py)?;
117 127 let path = HgPath::new(bytes.data(py));
118 128 let res = self.inner(py).borrow_mut().set_untracked(path);
119 129 let was_tracked = res.or_else(|_| {
120 130 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
121 131 })?;
122 132 Ok(was_tracked.to_py_object(py))
123 133 }
124 134
125 135 def set_clean(
126 136 &self,
127 137 f: PyObject,
128 138 mode: u32,
129 139 size: u32,
130 140 mtime: (i64, u32, bool)
131 141 ) -> PyResult<PyNone> {
132 142 let (mtime_s, mtime_ns, second_ambiguous) = mtime;
133 143 let timestamp = TruncatedTimestamp::new_truncate(
134 144 mtime_s, mtime_ns, second_ambiguous
135 145 );
136 146 let bytes = f.extract::<PyBytes>(py)?;
137 147 let path = HgPath::new(bytes.data(py));
138 148 let res = self.inner(py).borrow_mut().set_clean(
139 149 path, mode, size, timestamp,
140 150 );
141 151 res.or_else(|_| {
142 152 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
143 153 })?;
144 154 Ok(PyNone)
145 155 }
146 156
147 157 def set_possibly_dirty(&self, f: PyObject) -> PyResult<PyNone> {
148 158 let bytes = f.extract::<PyBytes>(py)?;
149 159 let path = HgPath::new(bytes.data(py));
150 160 let res = self.inner(py).borrow_mut().set_possibly_dirty(path);
151 161 res.or_else(|_| {
152 162 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
153 163 })?;
154 164 Ok(PyNone)
155 165 }
156 166
157 167 def reset_state(
158 168 &self,
159 169 f: PyObject,
160 170 wc_tracked: bool,
161 171 p1_tracked: bool,
162 172 p2_info: bool,
163 173 has_meaningful_mtime: bool,
164 174 parentfiledata: Option<(u32, u32, Option<(i64, u32, bool)>)>,
165 175 ) -> PyResult<PyNone> {
166 176 let mut has_meaningful_mtime = has_meaningful_mtime;
167 177 let parent_file_data = match parentfiledata {
168 178 None => {
169 179 has_meaningful_mtime = false;
170 180 None
171 181 },
172 182 Some(data) => {
173 183 let (mode, size, mtime_info) = data;
174 184 let mtime = if let Some(mtime_info) = mtime_info {
175 185 let (mtime_s, mtime_ns, second_ambiguous) = mtime_info;
176 186 let timestamp = TruncatedTimestamp::new_truncate(
177 187 mtime_s, mtime_ns, second_ambiguous
178 188 );
179 189 Some(timestamp)
180 190 } else {
181 191 has_meaningful_mtime = false;
182 192 None
183 193 };
184 194 Some(ParentFileData {
185 195 mode_size: Some((mode, size)),
186 196 mtime,
187 197 })
188 198 }
189 199 };
190 200 let bytes = f.extract::<PyBytes>(py)?;
191 201 let path = HgPath::new(bytes.data(py));
192 202 let res = self.inner(py).borrow_mut().reset_state(
193 203 path,
194 204 wc_tracked,
195 205 p1_tracked,
196 206 p2_info,
197 207 has_meaningful_mtime,
198 208 parent_file_data,
199 209 );
200 210 res.or_else(|_| {
201 211 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
202 212 })?;
203 213 Ok(PyNone)
204 214 }
205 215
206 216 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
207 217 let d = d.extract::<PyBytes>(py)?;
208 218 Ok(self.inner(py).borrow_mut()
209 219 .has_tracked_dir(HgPath::new(d.data(py)))
210 220 .map_err(|e| {
211 221 PyErr::new::<exc::ValueError, _>(py, e.to_string())
212 222 })?
213 223 .to_py_object(py))
214 224 }
215 225
216 226 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
217 227 let d = d.extract::<PyBytes>(py)?;
218 228 Ok(self.inner(py).borrow_mut()
219 229 .has_dir(HgPath::new(d.data(py)))
220 230 .map_err(|e| {
221 231 PyErr::new::<exc::ValueError, _>(py, e.to_string())
222 232 })?
223 233 .to_py_object(py))
224 234 }
225 235
226 236 def write_v1(
227 237 &self,
228 238 p1: PyObject,
229 239 p2: PyObject,
230 240 ) -> PyResult<PyBytes> {
231 241 let inner = self.inner(py).borrow();
232 242 let parents = DirstateParents {
233 243 p1: extract_node_id(py, &p1)?,
234 244 p2: extract_node_id(py, &p2)?,
235 245 };
236 246 let result = inner.pack_v1(parents);
237 247 match result {
238 248 Ok(packed) => Ok(PyBytes::new(py, &packed)),
239 249 Err(_) => Err(PyErr::new::<exc::OSError, _>(
240 250 py,
241 251 "Dirstate error".to_string(),
242 252 )),
243 253 }
244 254 }
245 255
246 256 /// Returns new data together with whether that data should be appended to
247 257 /// the existing data file whose content is at `self.on_disk` (True),
248 258 /// instead of written to a new data file (False).
249 259 def write_v2(
250 260 &self,
251 261 write_mode: usize,
252 262 ) -> PyResult<PyObject> {
253 263 let inner = self.inner(py).borrow();
254 264 let rust_write_mode = match write_mode {
255 265 0 => DirstateMapWriteMode::Auto,
256 266 1 => DirstateMapWriteMode::ForceNewDataFile,
257 267 2 => DirstateMapWriteMode::ForceAppend,
258 268 _ => DirstateMapWriteMode::Auto, // XXX should we error out?
259 269 };
260 270 let result = inner.pack_v2(rust_write_mode);
261 271 match result {
262 272 Ok((packed, tree_metadata, append, _old_data_size)) => {
263 273 let packed = PyBytes::new(py, &packed);
264 274 let tree_metadata = PyBytes::new(py, tree_metadata.as_bytes());
265 275 let tuple = (packed, tree_metadata, append);
266 276 Ok(tuple.to_py_object(py).into_object())
267 277 },
268 278 Err(_) => Err(PyErr::new::<exc::OSError, _>(
269 279 py,
270 280 "Dirstate error".to_string(),
271 281 )),
272 282 }
273 283 }
274 284
275 285 def filefoldmapasdict(&self) -> PyResult<PyDict> {
276 286 let dict = PyDict::new(py);
277 287 for item in self.inner(py).borrow_mut().iter() {
278 288 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
279 289 if !entry.removed() {
280 290 let key = normalize_case(path);
281 291 let value = path;
282 292 dict.set_item(
283 293 py,
284 294 PyBytes::new(py, key.as_bytes()).into_object(),
285 295 PyBytes::new(py, value.as_bytes()).into_object(),
286 296 )?;
287 297 }
288 298 }
289 299 Ok(dict)
290 300 }
291 301
292 302 def __len__(&self) -> PyResult<usize> {
293 303 Ok(self.inner(py).borrow().len())
294 304 }
295 305
296 306 def __contains__(&self, key: PyObject) -> PyResult<bool> {
297 307 let key = key.extract::<PyBytes>(py)?;
298 308 self.inner(py)
299 309 .borrow()
300 310 .contains_key(HgPath::new(key.data(py)))
301 311 .map_err(|e| v2_error(py, e))
302 312 }
303 313
304 314 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
305 315 let key = key.extract::<PyBytes>(py)?;
306 316 let key = HgPath::new(key.data(py));
307 317 match self
308 318 .inner(py)
309 319 .borrow()
310 320 .get(key)
311 321 .map_err(|e| v2_error(py, e))?
312 322 {
313 323 Some(entry) => {
314 324 Ok(DirstateItem::new_as_pyobject(py, entry)?)
315 325 },
316 326 None => Err(PyErr::new::<exc::KeyError, _>(
317 327 py,
318 328 String::from_utf8_lossy(key.as_bytes()),
319 329 )),
320 330 }
321 331 }
322 332
323 333 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
324 334 let leaked_ref = self.inner(py).leak_immutable();
325 335 DirstateMapKeysIterator::from_inner(
326 336 py,
327 337 unsafe { leaked_ref.map(py, |o| o.iter()) },
328 338 )
329 339 }
330 340
331 341 def items(&self) -> PyResult<DirstateMapItemsIterator> {
332 342 let leaked_ref = self.inner(py).leak_immutable();
333 343 DirstateMapItemsIterator::from_inner(
334 344 py,
335 345 unsafe { leaked_ref.map(py, |o| o.iter()) },
336 346 )
337 347 }
338 348
339 349 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
340 350 let leaked_ref = self.inner(py).leak_immutable();
341 351 DirstateMapKeysIterator::from_inner(
342 352 py,
343 353 unsafe { leaked_ref.map(py, |o| o.iter()) },
344 354 )
345 355 }
346 356
347 357 // TODO all copymap* methods, see docstring above
348 358 def copymapcopy(&self) -> PyResult<PyDict> {
349 359 let dict = PyDict::new(py);
350 360 for item in self.inner(py).borrow().copy_map_iter() {
351 361 let (key, value) = item.map_err(|e| v2_error(py, e))?;
352 362 dict.set_item(
353 363 py,
354 364 PyBytes::new(py, key.as_bytes()),
355 365 PyBytes::new(py, value.as_bytes()),
356 366 )?;
357 367 }
358 368 Ok(dict)
359 369 }
360 370
361 371 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
362 372 let key = key.extract::<PyBytes>(py)?;
363 373 match self
364 374 .inner(py)
365 375 .borrow()
366 376 .copy_map_get(HgPath::new(key.data(py)))
367 377 .map_err(|e| v2_error(py, e))?
368 378 {
369 379 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
370 380 None => Err(PyErr::new::<exc::KeyError, _>(
371 381 py,
372 382 String::from_utf8_lossy(key.data(py)),
373 383 )),
374 384 }
375 385 }
376 386 def copymap(&self) -> PyResult<CopyMap> {
377 387 CopyMap::from_inner(py, self.clone_ref(py))
378 388 }
379 389
380 390 def copymaplen(&self) -> PyResult<usize> {
381 391 Ok(self.inner(py).borrow().copy_map_len())
382 392 }
383 393 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
384 394 let key = key.extract::<PyBytes>(py)?;
385 395 self.inner(py)
386 396 .borrow()
387 397 .copy_map_contains_key(HgPath::new(key.data(py)))
388 398 .map_err(|e| v2_error(py, e))
389 399 }
390 400 def copymapget(
391 401 &self,
392 402 key: PyObject,
393 403 default: Option<PyObject>
394 404 ) -> PyResult<Option<PyObject>> {
395 405 let key = key.extract::<PyBytes>(py)?;
396 406 match self
397 407 .inner(py)
398 408 .borrow()
399 409 .copy_map_get(HgPath::new(key.data(py)))
400 410 .map_err(|e| v2_error(py, e))?
401 411 {
402 412 Some(copy) => Ok(Some(
403 413 PyBytes::new(py, copy.as_bytes()).into_object(),
404 414 )),
405 415 None => Ok(default),
406 416 }
407 417 }
408 418 def copymapsetitem(
409 419 &self,
410 420 key: PyObject,
411 421 value: PyObject
412 422 ) -> PyResult<PyObject> {
413 423 let key = key.extract::<PyBytes>(py)?;
414 424 let value = value.extract::<PyBytes>(py)?;
415 425 self.inner(py)
416 426 .borrow_mut()
417 427 .copy_map_insert(
418 428 HgPath::new(key.data(py)),
419 429 HgPath::new(value.data(py)),
420 430 )
421 431 .map_err(|e| v2_error(py, e))?;
422 432 Ok(py.None())
423 433 }
424 434 def copymappop(
425 435 &self,
426 436 key: PyObject,
427 437 default: Option<PyObject>
428 438 ) -> PyResult<Option<PyObject>> {
429 439 let key = key.extract::<PyBytes>(py)?;
430 440 match self
431 441 .inner(py)
432 442 .borrow_mut()
433 443 .copy_map_remove(HgPath::new(key.data(py)))
434 444 .map_err(|e| v2_error(py, e))?
435 445 {
436 446 Some(copy) => Ok(Some(
437 447 PyBytes::new(py, copy.as_bytes()).into_object(),
438 448 )),
439 449 None => Ok(default),
440 450 }
441 451 }
442 452
443 453 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
444 454 let leaked_ref = self.inner(py).leak_immutable();
445 455 CopyMapKeysIterator::from_inner(
446 456 py,
447 457 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
448 458 )
449 459 }
450 460
451 461 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
452 462 let leaked_ref = self.inner(py).leak_immutable();
453 463 CopyMapItemsIterator::from_inner(
454 464 py,
455 465 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
456 466 )
457 467 }
458 468
459 469 def tracked_dirs(&self) -> PyResult<PyList> {
460 470 let dirs = PyList::new(py, &[]);
461 471 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
462 472 .map_err(|e |dirstate_error(py, e))?
463 473 {
464 474 let path = path.map_err(|e| v2_error(py, e))?;
465 475 let path = PyBytes::new(py, path.as_bytes());
466 476 dirs.append(py, path.into_object())
467 477 }
468 478 Ok(dirs)
469 479 }
470 480
471 481 def setparents_fixup(&self) -> PyResult<PyDict> {
472 482 let dict = PyDict::new(py);
473 483 let copies = self.inner(py).borrow_mut().setparents_fixup();
474 484 for (key, value) in copies.map_err(|e| v2_error(py, e))? {
475 485 dict.set_item(
476 486 py,
477 487 PyBytes::new(py, key.as_bytes()),
478 488 PyBytes::new(py, value.as_bytes()),
479 489 )?;
480 490 }
481 491 Ok(dict)
482 492 }
483 493
484 494 def debug_iter(&self, all: bool) -> PyResult<PyList> {
485 495 let dirs = PyList::new(py, &[]);
486 496 for item in self.inner(py).borrow().debug_iter(all) {
487 497 let (path, (state, mode, size, mtime)) =
488 498 item.map_err(|e| v2_error(py, e))?;
489 499 let path = PyBytes::new(py, path.as_bytes());
490 500 let item = (path, state, mode, size, mtime);
491 501 dirs.append(py, item.to_py_object(py).into_object())
492 502 }
493 503 Ok(dirs)
494 504 }
495 505 });
496 506
497 507 impl DirstateMap {
498 508 pub fn get_inner_mut<'a>(
499 509 &'a self,
500 510 py: Python<'a>,
501 511 ) -> RefMut<'a, OwningDirstateMap> {
502 512 self.inner(py).borrow_mut()
503 513 }
504 514 fn translate_key(
505 515 py: Python,
506 516 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
507 517 ) -> PyResult<Option<PyBytes>> {
508 518 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
509 519 Ok(Some(PyBytes::new(py, f.as_bytes())))
510 520 }
511 521 fn translate_key_value(
512 522 py: Python,
513 523 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
514 524 ) -> PyResult<Option<(PyBytes, PyObject)>> {
515 525 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
516 526 Ok(Some((
517 527 PyBytes::new(py, f.as_bytes()),
518 528 DirstateItem::new_as_pyobject(py, entry)?,
519 529 )))
520 530 }
521 531 }
522 532
523 533 py_shared_iterator!(
524 534 DirstateMapKeysIterator,
525 535 UnsafePyLeaked<StateMapIter<'static>>,
526 536 DirstateMap::translate_key,
527 537 Option<PyBytes>
528 538 );
529 539
530 540 py_shared_iterator!(
531 541 DirstateMapItemsIterator,
532 542 UnsafePyLeaked<StateMapIter<'static>>,
533 543 DirstateMap::translate_key_value,
534 544 Option<(PyBytes, PyObject)>
535 545 );
536 546
537 547 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
538 548 let bytes = obj.extract::<PyBytes>(py)?;
539 549 match bytes.data(py).try_into() {
540 550 Ok(s) => Ok(s),
541 551 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
542 552 }
543 553 }
544 554
545 555 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
546 556 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
547 557 }
548 558
549 559 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
550 560 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
551 561 }
General Comments 0
You need to be logged in to leave comments. Login now