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