##// END OF EJS Templates
dirstate: Skip no-op conversion in Rust DirstateMap::set_v1...
Simon Sapin -
r48859:32ef6478 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 self._rustmap.set_v1(filename, entry)
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 698 return 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 self._rustmap.set_v1(filename, entry)
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 self._rustmap.set_v1(filename, entry)
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 self._rustmap.set_v1(key, value)
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 pub fn set_v1_inner(&mut self, filename: &HgPath, entry: DirstateEntry) {
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 201 ) -> Result<bool, 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 219 Ok(exists)
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,1309 +1,1309 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 fn set_v1(&mut self, filename: &HgPath, entry: DirstateEntry) {
761 fn set_entry(&mut self, filename: &HgPath, entry: DirstateEntry) {
762 762 let node =
763 763 self.get_or_insert(&filename).expect("no parse error in v1");
764 764 node.data = NodeData::Entry(entry);
765 765 node.children = ChildNodes::default();
766 766 node.copy_source = None;
767 767 node.descendants_with_entry_count = 0;
768 768 node.tracked_descendants_count = 0;
769 769 }
770 770
771 771 fn add_file(
772 772 &mut self,
773 773 filename: &HgPath,
774 774 entry: DirstateEntry,
775 775 added: bool,
776 776 merged: bool,
777 777 from_p2: bool,
778 778 possibly_dirty: bool,
779 779 ) -> Result<(), DirstateError> {
780 780 let state;
781 781 let size;
782 782 let mtime;
783 783 if added {
784 784 assert!(!possibly_dirty);
785 785 assert!(!from_p2);
786 786 state = EntryState::Added;
787 787 size = SIZE_NON_NORMAL;
788 788 mtime = MTIME_UNSET;
789 789 } else if merged {
790 790 assert!(!possibly_dirty);
791 791 assert!(!from_p2);
792 792 state = EntryState::Merged;
793 793 size = SIZE_FROM_OTHER_PARENT;
794 794 mtime = MTIME_UNSET;
795 795 } else if from_p2 {
796 796 assert!(!possibly_dirty);
797 797 state = EntryState::Normal;
798 798 size = SIZE_FROM_OTHER_PARENT;
799 799 mtime = MTIME_UNSET;
800 800 } else if possibly_dirty {
801 801 state = EntryState::Normal;
802 802 size = SIZE_NON_NORMAL;
803 803 mtime = MTIME_UNSET;
804 804 } else {
805 805 state = EntryState::Normal;
806 806 size = entry.size() & V1_RANGEMASK;
807 807 mtime = entry.mtime() & V1_RANGEMASK;
808 808 }
809 809 let mode = entry.mode();
810 810 let entry = DirstateEntry::from_v1_data(state, mode, size, mtime);
811 811
812 812 let old_state = self.get(filename)?.map(|e| e.state());
813 813
814 814 Ok(self.add_or_remove_file(filename, old_state, entry)?)
815 815 }
816 816
817 817 fn remove_file(
818 818 &mut self,
819 819 filename: &HgPath,
820 820 in_merge: bool,
821 821 ) -> Result<(), DirstateError> {
822 822 let old_entry_opt = self.get(filename)?;
823 823 let old_state = old_entry_opt.map(|e| e.state());
824 824 let mut size = 0;
825 825 if in_merge {
826 826 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
827 827 // during a merge. So I (marmoute) am not sure we need the
828 828 // conditionnal at all. Adding double checking this with assert
829 829 // would be nice.
830 830 if let Some(old_entry) = old_entry_opt {
831 831 // backup the previous state
832 832 if old_entry.state() == EntryState::Merged {
833 833 size = SIZE_NON_NORMAL;
834 834 } else if old_entry.state() == EntryState::Normal
835 835 && old_entry.size() == SIZE_FROM_OTHER_PARENT
836 836 {
837 837 // other parent
838 838 size = SIZE_FROM_OTHER_PARENT;
839 839 }
840 840 }
841 841 }
842 842 if size == 0 {
843 843 self.copy_map_remove(filename)?;
844 844 }
845 845 let entry = DirstateEntry::new_removed(size);
846 846 Ok(self.add_or_remove_file(filename, old_state, entry)?)
847 847 }
848 848
849 849 fn drop_file(&mut self, filename: &HgPath) -> Result<bool, DirstateError> {
850 850 let was_tracked = self
851 851 .get(filename)?
852 852 .map_or(false, |e| e.state().is_tracked());
853 853 struct Dropped {
854 854 was_tracked: bool,
855 855 had_entry: bool,
856 856 had_copy_source: bool,
857 857 }
858 858
859 859 /// If this returns `Ok(Some((dropped, removed)))`, then
860 860 ///
861 861 /// * `dropped` is about the leaf node that was at `filename`
862 862 /// * `removed` is whether this particular level of recursion just
863 863 /// removed a node in `nodes`.
864 864 fn recur<'on_disk>(
865 865 on_disk: &'on_disk [u8],
866 866 unreachable_bytes: &mut u32,
867 867 nodes: &mut ChildNodes<'on_disk>,
868 868 path: &HgPath,
869 869 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
870 870 let (first_path_component, rest_of_path) =
871 871 path.split_first_component();
872 872 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
873 873 let node = if let Some(node) = nodes.get_mut(first_path_component)
874 874 {
875 875 node
876 876 } else {
877 877 return Ok(None);
878 878 };
879 879 let dropped;
880 880 if let Some(rest) = rest_of_path {
881 881 if let Some((d, removed)) = recur(
882 882 on_disk,
883 883 unreachable_bytes,
884 884 &mut node.children,
885 885 rest,
886 886 )? {
887 887 dropped = d;
888 888 if dropped.had_entry {
889 889 node.descendants_with_entry_count -= 1;
890 890 }
891 891 if dropped.was_tracked {
892 892 node.tracked_descendants_count -= 1;
893 893 }
894 894
895 895 // Directory caches must be invalidated when removing a
896 896 // child node
897 897 if removed {
898 898 if let NodeData::CachedDirectory { .. } = &node.data {
899 899 node.data = NodeData::None
900 900 }
901 901 }
902 902 } else {
903 903 return Ok(None);
904 904 }
905 905 } else {
906 906 let had_entry = node.data.has_entry();
907 907 if had_entry {
908 908 node.data = NodeData::None
909 909 }
910 910 if let Some(source) = &node.copy_source {
911 911 DirstateMap::count_dropped_path(unreachable_bytes, source)
912 912 }
913 913 dropped = Dropped {
914 914 was_tracked: node
915 915 .data
916 916 .as_entry()
917 917 .map_or(false, |entry| entry.state().is_tracked()),
918 918 had_entry,
919 919 had_copy_source: node.copy_source.take().is_some(),
920 920 };
921 921 }
922 922 // After recursion, for both leaf (rest_of_path is None) nodes and
923 923 // parent nodes, remove a node if it just became empty.
924 924 let remove = !node.data.has_entry()
925 925 && node.copy_source.is_none()
926 926 && node.children.is_empty();
927 927 if remove {
928 928 let (key, _) =
929 929 nodes.remove_entry(first_path_component).unwrap();
930 930 DirstateMap::count_dropped_path(
931 931 unreachable_bytes,
932 932 key.full_path(),
933 933 )
934 934 }
935 935 Ok(Some((dropped, remove)))
936 936 }
937 937
938 938 if let Some((dropped, _removed)) = recur(
939 939 self.on_disk,
940 940 &mut self.unreachable_bytes,
941 941 &mut self.root,
942 942 filename,
943 943 )? {
944 944 if dropped.had_entry {
945 945 self.nodes_with_entry_count -= 1
946 946 }
947 947 if dropped.had_copy_source {
948 948 self.nodes_with_copy_source_count -= 1
949 949 }
950 950 Ok(dropped.had_entry)
951 951 } else {
952 952 debug_assert!(!was_tracked);
953 953 Ok(false)
954 954 }
955 955 }
956 956
957 957 fn clear_ambiguous_times(
958 958 &mut self,
959 959 filenames: Vec<HgPathBuf>,
960 960 now: i32,
961 961 ) -> Result<(), DirstateV2ParseError> {
962 962 for filename in filenames {
963 963 if let Some(node) = Self::get_node_mut(
964 964 self.on_disk,
965 965 &mut self.unreachable_bytes,
966 966 &mut self.root,
967 967 &filename,
968 968 )? {
969 969 if let NodeData::Entry(entry) = &mut node.data {
970 970 entry.clear_ambiguous_mtime(now);
971 971 }
972 972 }
973 973 }
974 974 Ok(())
975 975 }
976 976
977 977 fn non_normal_entries_contains(
978 978 &mut self,
979 979 key: &HgPath,
980 980 ) -> Result<bool, DirstateV2ParseError> {
981 981 Ok(if let Some(node) = self.get_node(key)? {
982 982 node.entry()?.map_or(false, |entry| entry.is_non_normal())
983 983 } else {
984 984 false
985 985 })
986 986 }
987 987
988 988 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool {
989 989 // Do nothing, this `DirstateMap` does not have a separate "non normal
990 990 // entries" set that need to be kept up to date.
991 991 if let Ok(Some(v)) = self.get(key) {
992 992 return v.is_non_normal();
993 993 }
994 994 false
995 995 }
996 996
997 997 fn non_normal_entries_add(&mut self, _key: &HgPath) {
998 998 // Do nothing, this `DirstateMap` does not have a separate "non normal
999 999 // entries" set that need to be kept up to date
1000 1000 }
1001 1001
1002 1002 fn non_normal_or_other_parent_paths(
1003 1003 &mut self,
1004 1004 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
1005 1005 {
1006 1006 Box::new(self.filter_full_paths(|entry| {
1007 1007 entry.is_non_normal() || entry.is_from_other_parent()
1008 1008 }))
1009 1009 }
1010 1010
1011 1011 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
1012 1012 // Do nothing, this `DirstateMap` does not have a separate "non normal
1013 1013 // entries" and "from other parent" sets that need to be recomputed
1014 1014 }
1015 1015
1016 1016 fn iter_non_normal_paths(
1017 1017 &mut self,
1018 1018 ) -> Box<
1019 1019 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
1020 1020 > {
1021 1021 self.iter_non_normal_paths_panic()
1022 1022 }
1023 1023
1024 1024 fn iter_non_normal_paths_panic(
1025 1025 &self,
1026 1026 ) -> Box<
1027 1027 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
1028 1028 > {
1029 1029 Box::new(self.filter_full_paths(|entry| entry.is_non_normal()))
1030 1030 }
1031 1031
1032 1032 fn iter_other_parent_paths(
1033 1033 &mut self,
1034 1034 ) -> Box<
1035 1035 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
1036 1036 > {
1037 1037 Box::new(self.filter_full_paths(|entry| entry.is_from_other_parent()))
1038 1038 }
1039 1039
1040 1040 fn has_tracked_dir(
1041 1041 &mut self,
1042 1042 directory: &HgPath,
1043 1043 ) -> Result<bool, DirstateError> {
1044 1044 if let Some(node) = self.get_node(directory)? {
1045 1045 // A node without a `DirstateEntry` was created to hold child
1046 1046 // nodes, and is therefore a directory.
1047 1047 let state = node.state()?;
1048 1048 Ok(state.is_none() && node.tracked_descendants_count() > 0)
1049 1049 } else {
1050 1050 Ok(false)
1051 1051 }
1052 1052 }
1053 1053
1054 1054 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
1055 1055 if let Some(node) = self.get_node(directory)? {
1056 1056 // A node without a `DirstateEntry` was created to hold child
1057 1057 // nodes, and is therefore a directory.
1058 1058 let state = node.state()?;
1059 1059 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
1060 1060 } else {
1061 1061 Ok(false)
1062 1062 }
1063 1063 }
1064 1064
1065 1065 #[timed]
1066 1066 fn pack_v1(
1067 1067 &mut self,
1068 1068 parents: DirstateParents,
1069 1069 now: Timestamp,
1070 1070 ) -> Result<Vec<u8>, DirstateError> {
1071 1071 let now: i32 = now.0.try_into().expect("time overflow");
1072 1072 let mut ambiguous_mtimes = Vec::new();
1073 1073 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1074 1074 // reallocations
1075 1075 let mut size = parents.as_bytes().len();
1076 1076 for node in self.iter_nodes() {
1077 1077 let node = node?;
1078 1078 if let Some(entry) = node.entry()? {
1079 1079 size += packed_entry_size(
1080 1080 node.full_path(self.on_disk)?,
1081 1081 node.copy_source(self.on_disk)?,
1082 1082 );
1083 1083 if entry.mtime_is_ambiguous(now) {
1084 1084 ambiguous_mtimes.push(
1085 1085 node.full_path_borrowed(self.on_disk)?
1086 1086 .detach_from_tree(),
1087 1087 )
1088 1088 }
1089 1089 }
1090 1090 }
1091 1091 self.clear_known_ambiguous_mtimes(&ambiguous_mtimes)?;
1092 1092
1093 1093 let mut packed = Vec::with_capacity(size);
1094 1094 packed.extend(parents.as_bytes());
1095 1095
1096 1096 for node in self.iter_nodes() {
1097 1097 let node = node?;
1098 1098 if let Some(entry) = node.entry()? {
1099 1099 pack_entry(
1100 1100 node.full_path(self.on_disk)?,
1101 1101 &entry,
1102 1102 node.copy_source(self.on_disk)?,
1103 1103 &mut packed,
1104 1104 );
1105 1105 }
1106 1106 }
1107 1107 Ok(packed)
1108 1108 }
1109 1109
1110 1110 /// Returns new data and metadata together with whether that data should be
1111 1111 /// appended to the existing data file whose content is at
1112 1112 /// `self.on_disk` (true), instead of written to a new data file
1113 1113 /// (false).
1114 1114 #[timed]
1115 1115 fn pack_v2(
1116 1116 &mut self,
1117 1117 now: Timestamp,
1118 1118 can_append: bool,
1119 1119 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
1120 1120 // TODO: how do we want to handle this in 2038?
1121 1121 let now: i32 = now.0.try_into().expect("time overflow");
1122 1122 let mut paths = Vec::new();
1123 1123 for node in self.iter_nodes() {
1124 1124 let node = node?;
1125 1125 if let Some(entry) = node.entry()? {
1126 1126 if entry.mtime_is_ambiguous(now) {
1127 1127 paths.push(
1128 1128 node.full_path_borrowed(self.on_disk)?
1129 1129 .detach_from_tree(),
1130 1130 )
1131 1131 }
1132 1132 }
1133 1133 }
1134 1134 // Borrow of `self` ends here since we collect cloned paths
1135 1135
1136 1136 self.clear_known_ambiguous_mtimes(&paths)?;
1137 1137
1138 1138 on_disk::write(self, can_append)
1139 1139 }
1140 1140
1141 1141 fn status<'a>(
1142 1142 &'a mut self,
1143 1143 matcher: &'a (dyn Matcher + Sync),
1144 1144 root_dir: PathBuf,
1145 1145 ignore_files: Vec<PathBuf>,
1146 1146 options: StatusOptions,
1147 1147 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
1148 1148 {
1149 1149 super::status::status(self, matcher, root_dir, ignore_files, options)
1150 1150 }
1151 1151
1152 1152 fn copy_map_len(&self) -> usize {
1153 1153 self.nodes_with_copy_source_count as usize
1154 1154 }
1155 1155
1156 1156 fn copy_map_iter(&self) -> CopyMapIter<'_> {
1157 1157 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1158 1158 Ok(if let Some(source) = node.copy_source(self.on_disk)? {
1159 1159 Some((node.full_path(self.on_disk)?, source))
1160 1160 } else {
1161 1161 None
1162 1162 })
1163 1163 }))
1164 1164 }
1165 1165
1166 1166 fn copy_map_contains_key(
1167 1167 &self,
1168 1168 key: &HgPath,
1169 1169 ) -> Result<bool, DirstateV2ParseError> {
1170 1170 Ok(if let Some(node) = self.get_node(key)? {
1171 1171 node.has_copy_source()
1172 1172 } else {
1173 1173 false
1174 1174 })
1175 1175 }
1176 1176
1177 1177 fn copy_map_get(
1178 1178 &self,
1179 1179 key: &HgPath,
1180 1180 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1181 1181 if let Some(node) = self.get_node(key)? {
1182 1182 if let Some(source) = node.copy_source(self.on_disk)? {
1183 1183 return Ok(Some(source));
1184 1184 }
1185 1185 }
1186 1186 Ok(None)
1187 1187 }
1188 1188
1189 1189 fn copy_map_remove(
1190 1190 &mut self,
1191 1191 key: &HgPath,
1192 1192 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1193 1193 let count = &mut self.nodes_with_copy_source_count;
1194 1194 let unreachable_bytes = &mut self.unreachable_bytes;
1195 1195 Ok(Self::get_node_mut(
1196 1196 self.on_disk,
1197 1197 unreachable_bytes,
1198 1198 &mut self.root,
1199 1199 key,
1200 1200 )?
1201 1201 .and_then(|node| {
1202 1202 if let Some(source) = &node.copy_source {
1203 1203 *count -= 1;
1204 1204 Self::count_dropped_path(unreachable_bytes, source);
1205 1205 }
1206 1206 node.copy_source.take().map(Cow::into_owned)
1207 1207 }))
1208 1208 }
1209 1209
1210 1210 fn copy_map_insert(
1211 1211 &mut self,
1212 1212 key: HgPathBuf,
1213 1213 value: HgPathBuf,
1214 1214 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1215 1215 let node = Self::get_or_insert_node(
1216 1216 self.on_disk,
1217 1217 &mut self.unreachable_bytes,
1218 1218 &mut self.root,
1219 1219 &key,
1220 1220 WithBasename::to_cow_owned,
1221 1221 |_ancestor| {},
1222 1222 )?;
1223 1223 if node.copy_source.is_none() {
1224 1224 self.nodes_with_copy_source_count += 1
1225 1225 }
1226 1226 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1227 1227 }
1228 1228
1229 1229 fn len(&self) -> usize {
1230 1230 self.nodes_with_entry_count as usize
1231 1231 }
1232 1232
1233 1233 fn contains_key(
1234 1234 &self,
1235 1235 key: &HgPath,
1236 1236 ) -> Result<bool, DirstateV2ParseError> {
1237 1237 Ok(self.get(key)?.is_some())
1238 1238 }
1239 1239
1240 1240 fn get(
1241 1241 &self,
1242 1242 key: &HgPath,
1243 1243 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1244 1244 Ok(if let Some(node) = self.get_node(key)? {
1245 1245 node.entry()?
1246 1246 } else {
1247 1247 None
1248 1248 })
1249 1249 }
1250 1250
1251 1251 fn iter(&self) -> StateMapIter<'_> {
1252 1252 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1253 1253 Ok(if let Some(entry) = node.entry()? {
1254 1254 Some((node.full_path(self.on_disk)?, entry))
1255 1255 } else {
1256 1256 None
1257 1257 })
1258 1258 }))
1259 1259 }
1260 1260
1261 1261 fn iter_tracked_dirs(
1262 1262 &mut self,
1263 1263 ) -> Result<
1264 1264 Box<
1265 1265 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1266 1266 + Send
1267 1267 + '_,
1268 1268 >,
1269 1269 DirstateError,
1270 1270 > {
1271 1271 let on_disk = self.on_disk;
1272 1272 Ok(Box::new(filter_map_results(
1273 1273 self.iter_nodes(),
1274 1274 move |node| {
1275 1275 Ok(if node.tracked_descendants_count() > 0 {
1276 1276 Some(node.full_path(on_disk)?)
1277 1277 } else {
1278 1278 None
1279 1279 })
1280 1280 },
1281 1281 )))
1282 1282 }
1283 1283
1284 1284 fn debug_iter(
1285 1285 &self,
1286 1286 all: bool,
1287 1287 ) -> Box<
1288 1288 dyn Iterator<
1289 1289 Item = Result<
1290 1290 (&HgPath, (u8, i32, i32, i32)),
1291 1291 DirstateV2ParseError,
1292 1292 >,
1293 1293 > + Send
1294 1294 + '_,
1295 1295 > {
1296 1296 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1297 1297 let debug_tuple = if let Some(entry) = node.entry()? {
1298 1298 entry.debug_tuple()
1299 1299 } else if !all {
1300 1300 return Ok(None);
1301 1301 } else if let Some(mtime) = node.cached_directory_mtime() {
1302 1302 (b' ', 0, -1, mtime.seconds() as i32)
1303 1303 } else {
1304 1304 (b' ', 0, -1, -1)
1305 1305 };
1306 1306 Ok(Some((node.full_path(self.on_disk)?, debug_tuple)))
1307 1307 }))
1308 1308 }
1309 1309 }
@@ -1,562 +1,564 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 fn set_v1(&mut self, filename: &HgPath, entry: DirstateEntry);
40 /// Add the given filename to the map if it is not already there, and
41 /// associate the given entry with it.
42 fn set_entry(&mut self, filename: &HgPath, entry: DirstateEntry);
41 43
42 44 /// Add or change the information associated to a given file.
43 45 ///
44 46 /// `old_state` is the state in the entry that `get` would have returned
45 47 /// before this call, or `EntryState::Unknown` if there was no such entry.
46 48 ///
47 49 /// `entry.state` should never be `EntryState::Unknown`.
48 50 fn add_file(
49 51 &mut self,
50 52 filename: &HgPath,
51 53 entry: DirstateEntry,
52 54 added: bool,
53 55 merged: bool,
54 56 from_p2: bool,
55 57 possibly_dirty: bool,
56 58 ) -> Result<(), DirstateError>;
57 59
58 60 /// Mark a file as "removed" (as in `hg rm`).
59 61 ///
60 62 /// `old_state` is the state in the entry that `get` would have returned
61 63 /// before this call, or `EntryState::Unknown` if there was no such entry.
62 64 ///
63 65 /// `size` is not actually a size but the 0 or -1 or -2 value that would be
64 66 /// put in the size field in the dirstate-v1 format.
65 67 fn remove_file(
66 68 &mut self,
67 69 filename: &HgPath,
68 70 in_merge: bool,
69 71 ) -> Result<(), DirstateError>;
70 72
71 73 /// Drop information about this file from the map if any, and return
72 74 /// whether there was any.
73 75 ///
74 76 /// `get` will now return `None` for this filename.
75 77 ///
76 78 /// `old_state` is the state in the entry that `get` would have returned
77 79 /// before this call, or `EntryState::Unknown` if there was no such entry.
78 80 fn drop_file(&mut self, filename: &HgPath) -> Result<bool, DirstateError>;
79 81
80 82 /// Among given files, mark the stored `mtime` as ambiguous if there is one
81 83 /// (if `state == EntryState::Normal`) equal to the given current Unix
82 84 /// timestamp.
83 85 fn clear_ambiguous_times(
84 86 &mut self,
85 87 filenames: Vec<HgPathBuf>,
86 88 now: i32,
87 89 ) -> Result<(), DirstateV2ParseError>;
88 90
89 91 /// Return whether the map has an "non-normal" entry for the given
90 92 /// filename. That is, any entry with a `state` other than
91 93 /// `EntryState::Normal` or with an ambiguous `mtime`.
92 94 fn non_normal_entries_contains(
93 95 &mut self,
94 96 key: &HgPath,
95 97 ) -> Result<bool, DirstateV2ParseError>;
96 98
97 99 /// Mark the given path as "normal" file. This is only relevant in the flat
98 100 /// dirstate map where there is a separate `HashSet` that needs to be kept
99 101 /// up to date.
100 102 /// Returns whether the key was present in the set.
101 103 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool;
102 104
103 105 /// Mark the given path as "non-normal" file.
104 106 /// This is only relevant in the flat dirstate map where there is a
105 107 /// separate `HashSet` that needs to be kept up to date.
106 108 fn non_normal_entries_add(&mut self, key: &HgPath);
107 109
108 110 /// Return an iterator of paths whose respective entry are either
109 111 /// "non-normal" (see `non_normal_entries_contains`) or "from other
110 112 /// parent".
111 113 ///
112 114 /// If that information is cached, create the cache as needed.
113 115 ///
114 116 /// "From other parent" is defined as `state == Normal && size == -2`.
115 117 ///
116 118 /// Because parse errors can happen during iteration, the iterated items
117 119 /// are `Result`s.
118 120 fn non_normal_or_other_parent_paths(
119 121 &mut self,
120 122 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>;
121 123
122 124 /// Create the cache for `non_normal_or_other_parent_paths` if needed.
123 125 ///
124 126 /// If `force` is true, the cache is re-created even if it already exists.
125 127 fn set_non_normal_other_parent_entries(&mut self, force: bool);
126 128
127 129 /// Return an iterator of paths whose respective entry are "non-normal"
128 130 /// (see `non_normal_entries_contains`).
129 131 ///
130 132 /// If that information is cached, create the cache as needed.
131 133 ///
132 134 /// Because parse errors can happen during iteration, the iterated items
133 135 /// are `Result`s.
134 136 fn iter_non_normal_paths(
135 137 &mut self,
136 138 ) -> Box<
137 139 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
138 140 >;
139 141
140 142 /// Same as `iter_non_normal_paths`, but takes `&self` instead of `&mut
141 143 /// self`.
142 144 ///
143 145 /// Panics if a cache is necessary but does not exist yet.
144 146 fn iter_non_normal_paths_panic(
145 147 &self,
146 148 ) -> Box<
147 149 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
148 150 >;
149 151
150 152 /// Return an iterator of paths whose respective entry are "from other
151 153 /// parent".
152 154 ///
153 155 /// If that information is cached, create the cache as needed.
154 156 ///
155 157 /// "From other parent" is defined as `state == Normal && size == -2`.
156 158 ///
157 159 /// Because parse errors can happen during iteration, the iterated items
158 160 /// are `Result`s.
159 161 fn iter_other_parent_paths(
160 162 &mut self,
161 163 ) -> Box<
162 164 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
163 165 >;
164 166
165 167 /// Returns whether the sub-tree rooted at the given directory contains any
166 168 /// tracked file.
167 169 ///
168 170 /// A file is tracked if it has a `state` other than `EntryState::Removed`.
169 171 fn has_tracked_dir(
170 172 &mut self,
171 173 directory: &HgPath,
172 174 ) -> Result<bool, DirstateError>;
173 175
174 176 /// Returns whether the sub-tree rooted at the given directory contains any
175 177 /// file with a dirstate entry.
176 178 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError>;
177 179
178 180 /// Clear mtimes that are ambigous with `now` (similar to
179 181 /// `clear_ambiguous_times` but for all files in the dirstate map), and
180 182 /// serialize bytes to write the `.hg/dirstate` file to disk in dirstate-v1
181 183 /// format.
182 184 fn pack_v1(
183 185 &mut self,
184 186 parents: DirstateParents,
185 187 now: Timestamp,
186 188 ) -> Result<Vec<u8>, DirstateError>;
187 189
188 190 /// Clear mtimes that are ambigous with `now` (similar to
189 191 /// `clear_ambiguous_times` but for all files in the dirstate map), and
190 192 /// serialize bytes to write a dirstate data file to disk in dirstate-v2
191 193 /// format.
192 194 ///
193 195 /// Returns new data and metadata together with whether that data should be
194 196 /// appended to the existing data file whose content is at
195 197 /// `self.on_disk` (true), instead of written to a new data file
196 198 /// (false).
197 199 ///
198 200 /// Note: this is only supported by the tree dirstate map.
199 201 fn pack_v2(
200 202 &mut self,
201 203 now: Timestamp,
202 204 can_append: bool,
203 205 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError>;
204 206
205 207 /// Run the status algorithm.
206 208 ///
207 209 /// This is not sematically a method of the dirstate map, but a different
208 210 /// algorithm is used for the flat v.s. tree dirstate map so having it in
209 211 /// this trait enables the same dynamic dispatch as with other methods.
210 212 fn status<'a>(
211 213 &'a mut self,
212 214 matcher: &'a (dyn Matcher + Sync),
213 215 root_dir: PathBuf,
214 216 ignore_files: Vec<PathBuf>,
215 217 options: StatusOptions,
216 218 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
217 219
218 220 /// Returns how many files in the dirstate map have a recorded copy source.
219 221 fn copy_map_len(&self) -> usize;
220 222
221 223 /// Returns an iterator of `(path, copy_source)` for all files that have a
222 224 /// copy source.
223 225 fn copy_map_iter(&self) -> CopyMapIter<'_>;
224 226
225 227 /// Returns whether the givef file has a copy source.
226 228 fn copy_map_contains_key(
227 229 &self,
228 230 key: &HgPath,
229 231 ) -> Result<bool, DirstateV2ParseError>;
230 232
231 233 /// Returns the copy source for the given file.
232 234 fn copy_map_get(
233 235 &self,
234 236 key: &HgPath,
235 237 ) -> Result<Option<&HgPath>, DirstateV2ParseError>;
236 238
237 239 /// Removes the recorded copy source if any for the given file, and returns
238 240 /// it.
239 241 fn copy_map_remove(
240 242 &mut self,
241 243 key: &HgPath,
242 244 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError>;
243 245
244 246 /// Set the given `value` copy source for the given `key` file.
245 247 fn copy_map_insert(
246 248 &mut self,
247 249 key: HgPathBuf,
248 250 value: HgPathBuf,
249 251 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError>;
250 252
251 253 /// Returns the number of files that have an entry.
252 254 fn len(&self) -> usize;
253 255
254 256 /// Returns whether the given file has an entry.
255 257 fn contains_key(&self, key: &HgPath)
256 258 -> Result<bool, DirstateV2ParseError>;
257 259
258 260 /// Returns the entry, if any, for the given file.
259 261 fn get(
260 262 &self,
261 263 key: &HgPath,
262 264 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError>;
263 265
264 266 /// Returns a `(path, entry)` iterator of files that have an entry.
265 267 ///
266 268 /// Because parse errors can happen during iteration, the iterated items
267 269 /// are `Result`s.
268 270 fn iter(&self) -> StateMapIter<'_>;
269 271
270 272 /// Returns an iterator of tracked directories.
271 273 ///
272 274 /// This is the paths for which `has_tracked_dir` would return true.
273 275 /// Or, in other words, the union of ancestor paths of all paths that have
274 276 /// an associated entry in a "tracked" state in this dirstate map.
275 277 ///
276 278 /// Because parse errors can happen during iteration, the iterated items
277 279 /// are `Result`s.
278 280 fn iter_tracked_dirs(
279 281 &mut self,
280 282 ) -> Result<
281 283 Box<
282 284 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
283 285 + Send
284 286 + '_,
285 287 >,
286 288 DirstateError,
287 289 >;
288 290
289 291 /// Return an iterator of `(path, (state, mode, size, mtime))` for every
290 292 /// node stored in this dirstate map, for the purpose of the `hg
291 293 /// debugdirstate` command.
292 294 ///
293 295 /// If `all` is true, include nodes that don’t have an entry.
294 296 /// For such nodes `state` is the ASCII space.
295 297 /// An `mtime` may still be present. It is used to optimize `status`.
296 298 ///
297 299 /// Because parse errors can happen during iteration, the iterated items
298 300 /// are `Result`s.
299 301 fn debug_iter(
300 302 &self,
301 303 all: bool,
302 304 ) -> Box<
303 305 dyn Iterator<
304 306 Item = Result<
305 307 (&HgPath, (u8, i32, i32, i32)),
306 308 DirstateV2ParseError,
307 309 >,
308 310 > + Send
309 311 + '_,
310 312 >;
311 313 }
312 314
313 315 impl DirstateMapMethods for DirstateMap {
314 316 fn clear(&mut self) {
315 317 self.clear()
316 318 }
317 319
318 320 /// Used to set a value directory.
319 321 ///
320 322 /// XXX Is temporary during a refactor of V1 dirstate and will disappear
321 323 /// shortly.
322 fn set_v1(&mut self, filename: &HgPath, entry: DirstateEntry) {
323 self.set_v1_inner(&filename, entry)
324 fn set_entry(&mut self, filename: &HgPath, entry: DirstateEntry) {
325 self.set_entry(&filename, entry)
324 326 }
325 327
326 328 fn add_file(
327 329 &mut self,
328 330 filename: &HgPath,
329 331 entry: DirstateEntry,
330 332 added: bool,
331 333 merged: bool,
332 334 from_p2: bool,
333 335 possibly_dirty: bool,
334 336 ) -> Result<(), DirstateError> {
335 337 self.add_file(filename, entry, added, merged, from_p2, possibly_dirty)
336 338 }
337 339
338 340 fn remove_file(
339 341 &mut self,
340 342 filename: &HgPath,
341 343 in_merge: bool,
342 344 ) -> Result<(), DirstateError> {
343 345 self.remove_file(filename, in_merge)
344 346 }
345 347
346 348 fn drop_file(&mut self, filename: &HgPath) -> Result<bool, DirstateError> {
347 349 self.drop_file(filename)
348 350 }
349 351
350 352 fn clear_ambiguous_times(
351 353 &mut self,
352 354 filenames: Vec<HgPathBuf>,
353 355 now: i32,
354 356 ) -> Result<(), DirstateV2ParseError> {
355 357 Ok(self.clear_ambiguous_times(filenames, now))
356 358 }
357 359
358 360 fn non_normal_entries_contains(
359 361 &mut self,
360 362 key: &HgPath,
361 363 ) -> Result<bool, DirstateV2ParseError> {
362 364 let (non_normal, _other_parent) =
363 365 self.get_non_normal_other_parent_entries();
364 366 Ok(non_normal.contains(key))
365 367 }
366 368
367 369 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool {
368 370 self.non_normal_entries_remove(key)
369 371 }
370 372
371 373 fn non_normal_entries_add(&mut self, key: &HgPath) {
372 374 self.non_normal_entries_add(key)
373 375 }
374 376
375 377 fn non_normal_or_other_parent_paths(
376 378 &mut self,
377 379 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
378 380 {
379 381 let (non_normal, other_parent) =
380 382 self.get_non_normal_other_parent_entries();
381 383 Box::new(non_normal.union(other_parent).map(|p| Ok(&**p)))
382 384 }
383 385
384 386 fn set_non_normal_other_parent_entries(&mut self, force: bool) {
385 387 self.set_non_normal_other_parent_entries(force)
386 388 }
387 389
388 390 fn iter_non_normal_paths(
389 391 &mut self,
390 392 ) -> Box<
391 393 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
392 394 > {
393 395 let (non_normal, _other_parent) =
394 396 self.get_non_normal_other_parent_entries();
395 397 Box::new(non_normal.iter().map(|p| Ok(&**p)))
396 398 }
397 399
398 400 fn iter_non_normal_paths_panic(
399 401 &self,
400 402 ) -> Box<
401 403 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
402 404 > {
403 405 let (non_normal, _other_parent) =
404 406 self.get_non_normal_other_parent_entries_panic();
405 407 Box::new(non_normal.iter().map(|p| Ok(&**p)))
406 408 }
407 409
408 410 fn iter_other_parent_paths(
409 411 &mut self,
410 412 ) -> Box<
411 413 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
412 414 > {
413 415 let (_non_normal, other_parent) =
414 416 self.get_non_normal_other_parent_entries();
415 417 Box::new(other_parent.iter().map(|p| Ok(&**p)))
416 418 }
417 419
418 420 fn has_tracked_dir(
419 421 &mut self,
420 422 directory: &HgPath,
421 423 ) -> Result<bool, DirstateError> {
422 424 self.has_tracked_dir(directory)
423 425 }
424 426
425 427 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
426 428 self.has_dir(directory)
427 429 }
428 430
429 431 fn pack_v1(
430 432 &mut self,
431 433 parents: DirstateParents,
432 434 now: Timestamp,
433 435 ) -> Result<Vec<u8>, DirstateError> {
434 436 self.pack(parents, now)
435 437 }
436 438
437 439 fn pack_v2(
438 440 &mut self,
439 441 _now: Timestamp,
440 442 _can_append: bool,
441 443 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
442 444 panic!(
443 445 "should have used dirstate_tree::DirstateMap to use the v2 format"
444 446 )
445 447 }
446 448
447 449 fn status<'a>(
448 450 &'a mut self,
449 451 matcher: &'a (dyn Matcher + Sync),
450 452 root_dir: PathBuf,
451 453 ignore_files: Vec<PathBuf>,
452 454 options: StatusOptions,
453 455 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
454 456 {
455 457 crate::status(self, matcher, root_dir, ignore_files, options)
456 458 }
457 459
458 460 fn copy_map_len(&self) -> usize {
459 461 self.copy_map.len()
460 462 }
461 463
462 464 fn copy_map_iter(&self) -> CopyMapIter<'_> {
463 465 Box::new(
464 466 self.copy_map
465 467 .iter()
466 468 .map(|(key, value)| Ok((&**key, &**value))),
467 469 )
468 470 }
469 471
470 472 fn copy_map_contains_key(
471 473 &self,
472 474 key: &HgPath,
473 475 ) -> Result<bool, DirstateV2ParseError> {
474 476 Ok(self.copy_map.contains_key(key))
475 477 }
476 478
477 479 fn copy_map_get(
478 480 &self,
479 481 key: &HgPath,
480 482 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
481 483 Ok(self.copy_map.get(key).map(|p| &**p))
482 484 }
483 485
484 486 fn copy_map_remove(
485 487 &mut self,
486 488 key: &HgPath,
487 489 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
488 490 Ok(self.copy_map.remove(key))
489 491 }
490 492
491 493 fn copy_map_insert(
492 494 &mut self,
493 495 key: HgPathBuf,
494 496 value: HgPathBuf,
495 497 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
496 498 Ok(self.copy_map.insert(key, value))
497 499 }
498 500
499 501 fn len(&self) -> usize {
500 502 (&**self).len()
501 503 }
502 504
503 505 fn contains_key(
504 506 &self,
505 507 key: &HgPath,
506 508 ) -> Result<bool, DirstateV2ParseError> {
507 509 Ok((&**self).contains_key(key))
508 510 }
509 511
510 512 fn get(
511 513 &self,
512 514 key: &HgPath,
513 515 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
514 516 Ok((&**self).get(key).cloned())
515 517 }
516 518
517 519 fn iter(&self) -> StateMapIter<'_> {
518 520 Box::new((&**self).iter().map(|(key, value)| Ok((&**key, *value))))
519 521 }
520 522
521 523 fn iter_tracked_dirs(
522 524 &mut self,
523 525 ) -> Result<
524 526 Box<
525 527 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
526 528 + Send
527 529 + '_,
528 530 >,
529 531 DirstateError,
530 532 > {
531 533 self.set_all_dirs()?;
532 534 Ok(Box::new(
533 535 self.all_dirs
534 536 .as_ref()
535 537 .unwrap()
536 538 .iter()
537 539 .map(|path| Ok(&**path)),
538 540 ))
539 541 }
540 542
541 543 fn debug_iter(
542 544 &self,
543 545 all: bool,
544 546 ) -> Box<
545 547 dyn Iterator<
546 548 Item = Result<
547 549 (&HgPath, (u8, i32, i32, i32)),
548 550 DirstateV2ParseError,
549 551 >,
550 552 > + Send
551 553 + '_,
552 554 > {
553 555 // Not used for the flat (not tree-based) DirstateMap
554 556 let _ = all;
555 557
556 558 Box::new(
557 559 (&**self)
558 560 .iter()
559 561 .map(|(path, entry)| Ok((&**path, entry.debug_tuple()))),
560 562 )
561 563 }
562 564 }
@@ -1,241 +1,241 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 fn set_v1(&mut self, filename: &HgPath, entry: DirstateEntry) {
24 self.get_mut().set_v1(filename, entry)
23 fn set_entry(&mut self, filename: &HgPath, entry: DirstateEntry) {
24 self.get_mut().set_entry(filename, entry)
25 25 }
26 26
27 27 fn add_file(
28 28 &mut self,
29 29 filename: &HgPath,
30 30 entry: DirstateEntry,
31 31 added: bool,
32 32 merged: bool,
33 33 from_p2: bool,
34 34 possibly_dirty: bool,
35 35 ) -> Result<(), DirstateError> {
36 36 self.get_mut().add_file(
37 37 filename,
38 38 entry,
39 39 added,
40 40 merged,
41 41 from_p2,
42 42 possibly_dirty,
43 43 )
44 44 }
45 45
46 46 fn remove_file(
47 47 &mut self,
48 48 filename: &HgPath,
49 49 in_merge: bool,
50 50 ) -> Result<(), DirstateError> {
51 51 self.get_mut().remove_file(filename, in_merge)
52 52 }
53 53
54 54 fn drop_file(&mut self, filename: &HgPath) -> Result<bool, DirstateError> {
55 55 self.get_mut().drop_file(filename)
56 56 }
57 57
58 58 fn clear_ambiguous_times(
59 59 &mut self,
60 60 filenames: Vec<HgPathBuf>,
61 61 now: i32,
62 62 ) -> Result<(), DirstateV2ParseError> {
63 63 self.get_mut().clear_ambiguous_times(filenames, now)
64 64 }
65 65
66 66 fn non_normal_entries_contains(
67 67 &mut self,
68 68 key: &HgPath,
69 69 ) -> Result<bool, DirstateV2ParseError> {
70 70 self.get_mut().non_normal_entries_contains(key)
71 71 }
72 72
73 73 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool {
74 74 self.get_mut().non_normal_entries_remove(key)
75 75 }
76 76
77 77 fn non_normal_entries_add(&mut self, key: &HgPath) {
78 78 self.get_mut().non_normal_entries_add(key)
79 79 }
80 80
81 81 fn non_normal_or_other_parent_paths(
82 82 &mut self,
83 83 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
84 84 {
85 85 self.get_mut().non_normal_or_other_parent_paths()
86 86 }
87 87
88 88 fn set_non_normal_other_parent_entries(&mut self, force: bool) {
89 89 self.get_mut().set_non_normal_other_parent_entries(force)
90 90 }
91 91
92 92 fn iter_non_normal_paths(
93 93 &mut self,
94 94 ) -> Box<
95 95 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
96 96 > {
97 97 self.get_mut().iter_non_normal_paths()
98 98 }
99 99
100 100 fn iter_non_normal_paths_panic(
101 101 &self,
102 102 ) -> Box<
103 103 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
104 104 > {
105 105 self.get().iter_non_normal_paths_panic()
106 106 }
107 107
108 108 fn iter_other_parent_paths(
109 109 &mut self,
110 110 ) -> Box<
111 111 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
112 112 > {
113 113 self.get_mut().iter_other_parent_paths()
114 114 }
115 115
116 116 fn has_tracked_dir(
117 117 &mut self,
118 118 directory: &HgPath,
119 119 ) -> Result<bool, DirstateError> {
120 120 self.get_mut().has_tracked_dir(directory)
121 121 }
122 122
123 123 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
124 124 self.get_mut().has_dir(directory)
125 125 }
126 126
127 127 fn pack_v1(
128 128 &mut self,
129 129 parents: DirstateParents,
130 130 now: Timestamp,
131 131 ) -> Result<Vec<u8>, DirstateError> {
132 132 self.get_mut().pack_v1(parents, now)
133 133 }
134 134
135 135 fn pack_v2(
136 136 &mut self,
137 137 now: Timestamp,
138 138 can_append: bool,
139 139 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
140 140 self.get_mut().pack_v2(now, can_append)
141 141 }
142 142
143 143 fn status<'a>(
144 144 &'a mut self,
145 145 matcher: &'a (dyn Matcher + Sync),
146 146 root_dir: PathBuf,
147 147 ignore_files: Vec<PathBuf>,
148 148 options: StatusOptions,
149 149 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
150 150 {
151 151 self.get_mut()
152 152 .status(matcher, root_dir, ignore_files, options)
153 153 }
154 154
155 155 fn copy_map_len(&self) -> usize {
156 156 self.get().copy_map_len()
157 157 }
158 158
159 159 fn copy_map_iter(&self) -> CopyMapIter<'_> {
160 160 self.get().copy_map_iter()
161 161 }
162 162
163 163 fn copy_map_contains_key(
164 164 &self,
165 165 key: &HgPath,
166 166 ) -> Result<bool, DirstateV2ParseError> {
167 167 self.get().copy_map_contains_key(key)
168 168 }
169 169
170 170 fn copy_map_get(
171 171 &self,
172 172 key: &HgPath,
173 173 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
174 174 self.get().copy_map_get(key)
175 175 }
176 176
177 177 fn copy_map_remove(
178 178 &mut self,
179 179 key: &HgPath,
180 180 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
181 181 self.get_mut().copy_map_remove(key)
182 182 }
183 183
184 184 fn copy_map_insert(
185 185 &mut self,
186 186 key: HgPathBuf,
187 187 value: HgPathBuf,
188 188 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
189 189 self.get_mut().copy_map_insert(key, value)
190 190 }
191 191
192 192 fn len(&self) -> usize {
193 193 self.get().len()
194 194 }
195 195
196 196 fn contains_key(
197 197 &self,
198 198 key: &HgPath,
199 199 ) -> Result<bool, DirstateV2ParseError> {
200 200 self.get().contains_key(key)
201 201 }
202 202
203 203 fn get(
204 204 &self,
205 205 key: &HgPath,
206 206 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
207 207 self.get().get(key)
208 208 }
209 209
210 210 fn iter(&self) -> StateMapIter<'_> {
211 211 self.get().iter()
212 212 }
213 213
214 214 fn iter_tracked_dirs(
215 215 &mut self,
216 216 ) -> Result<
217 217 Box<
218 218 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
219 219 + Send
220 220 + '_,
221 221 >,
222 222 DirstateError,
223 223 > {
224 224 self.get_mut().iter_tracked_dirs()
225 225 }
226 226
227 227 fn debug_iter(
228 228 &self,
229 229 all: bool,
230 230 ) -> Box<
231 231 dyn Iterator<
232 232 Item = Result<
233 233 (&HgPath, (u8, i32, i32, i32)),
234 234 DirstateV2ParseError,
235 235 >,
236 236 > + Send
237 237 + '_,
238 238 > {
239 239 self.get().debug_iter(all)
240 240 }
241 241 }
@@ -1,675 +1,671 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 16 PyObject, PyResult, PySet, PyString, Python, PythonObject, ToPyObject,
17 17 UnsafePyLeaked,
18 18 };
19 19
20 20 use crate::{
21 21 dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
22 22 dirstate::item::DirstateItem,
23 23 dirstate::non_normal_entries::{
24 24 NonNormalEntries, NonNormalEntriesIterator,
25 25 },
26 26 pybytes_deref::PyBytesDeref,
27 27 };
28 28 use hg::{
29 29 dirstate::parsers::Timestamp,
30 30 dirstate::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 def set_v1(&self, path: PyObject, item: PyObject) -> PyResult<PyObject> {
132 def set_dirstate_item(
133 &self,
134 path: PyObject,
135 item: DirstateItem
136 ) -> PyResult<PyObject> {
133 137 let f = path.extract::<PyBytes>(py)?;
134 138 let filename = HgPath::new(f.data(py));
135 let state = item.getattr(py, "state")?.extract::<PyBytes>(py)?;
136 let state = state.data(py)[0];
137 let entry = DirstateEntry::from_v1_data(
138 state.try_into().expect("state is always valid"),
139 item.getattr(py, "mode")?.extract(py)?,
140 item.getattr(py, "size")?.extract(py)?,
141 item.getattr(py, "mtime")?.extract(py)?,
142 );
143 self.inner(py).borrow_mut().set_v1(filename, entry);
139 self.inner(py).borrow_mut().set_entry(filename, item.get_entry(py));
144 140 Ok(py.None())
145 141 }
146 142
147 143 def addfile(
148 144 &self,
149 145 f: PyObject,
150 146 mode: PyObject,
151 147 size: PyObject,
152 148 mtime: PyObject,
153 149 added: PyObject,
154 150 merged: PyObject,
155 151 from_p2: PyObject,
156 152 possibly_dirty: PyObject,
157 153 ) -> PyResult<PyObject> {
158 154 let f = f.extract::<PyBytes>(py)?;
159 155 let filename = HgPath::new(f.data(py));
160 156 let mode = if mode.is_none(py) {
161 157 // fallback default value
162 158 0
163 159 } else {
164 160 mode.extract(py)?
165 161 };
166 162 let size = if size.is_none(py) {
167 163 // fallback default value
168 164 SIZE_NON_NORMAL
169 165 } else {
170 166 size.extract(py)?
171 167 };
172 168 let mtime = if mtime.is_none(py) {
173 169 // fallback default value
174 170 MTIME_UNSET
175 171 } else {
176 172 mtime.extract(py)?
177 173 };
178 174 let entry = DirstateEntry::new_for_add_file(mode, size, mtime);
179 175 let added = added.extract::<PyBool>(py)?.is_true();
180 176 let merged = merged.extract::<PyBool>(py)?.is_true();
181 177 let from_p2 = from_p2.extract::<PyBool>(py)?.is_true();
182 178 let possibly_dirty = possibly_dirty.extract::<PyBool>(py)?.is_true();
183 179 self.inner(py).borrow_mut().add_file(
184 180 filename,
185 181 entry,
186 182 added,
187 183 merged,
188 184 from_p2,
189 185 possibly_dirty
190 186 ).and(Ok(py.None())).or_else(|e: DirstateError| {
191 187 Err(PyErr::new::<exc::ValueError, _>(py, e.to_string()))
192 188 })
193 189 }
194 190
195 191 def removefile(
196 192 &self,
197 193 f: PyObject,
198 194 in_merge: PyObject
199 195 ) -> PyResult<PyObject> {
200 196 self.inner(py).borrow_mut()
201 197 .remove_file(
202 198 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
203 199 in_merge.extract::<PyBool>(py)?.is_true(),
204 200 )
205 201 .or_else(|_| {
206 202 Err(PyErr::new::<exc::OSError, _>(
207 203 py,
208 204 "Dirstate error".to_string(),
209 205 ))
210 206 })?;
211 207 Ok(py.None())
212 208 }
213 209
214 210 def dropfile(
215 211 &self,
216 212 f: PyObject,
217 213 ) -> PyResult<PyBool> {
218 214 self.inner(py).borrow_mut()
219 215 .drop_file(
220 216 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
221 217 )
222 218 .and_then(|b| Ok(b.to_py_object(py)))
223 219 .or_else(|e| {
224 220 Err(PyErr::new::<exc::OSError, _>(
225 221 py,
226 222 format!("Dirstate error: {}", e.to_string()),
227 223 ))
228 224 })
229 225 }
230 226
231 227 def clearambiguoustimes(
232 228 &self,
233 229 files: PyObject,
234 230 now: PyObject
235 231 ) -> PyResult<PyObject> {
236 232 let files: PyResult<Vec<HgPathBuf>> = files
237 233 .iter(py)?
238 234 .map(|filename| {
239 235 Ok(HgPathBuf::from_bytes(
240 236 filename?.extract::<PyBytes>(py)?.data(py),
241 237 ))
242 238 })
243 239 .collect();
244 240 self.inner(py)
245 241 .borrow_mut()
246 242 .clear_ambiguous_times(files?, now.extract(py)?)
247 243 .map_err(|e| v2_error(py, e))?;
248 244 Ok(py.None())
249 245 }
250 246
251 247 def other_parent_entries(&self) -> PyResult<PyObject> {
252 248 let mut inner_shared = self.inner(py).borrow_mut();
253 249 let set = PySet::empty(py)?;
254 250 for path in inner_shared.iter_other_parent_paths() {
255 251 let path = path.map_err(|e| v2_error(py, e))?;
256 252 set.add(py, PyBytes::new(py, path.as_bytes()))?;
257 253 }
258 254 Ok(set.into_object())
259 255 }
260 256
261 257 def non_normal_entries(&self) -> PyResult<NonNormalEntries> {
262 258 NonNormalEntries::from_inner(py, self.clone_ref(py))
263 259 }
264 260
265 261 def non_normal_entries_contains(&self, key: PyObject) -> PyResult<bool> {
266 262 let key = key.extract::<PyBytes>(py)?;
267 263 self.inner(py)
268 264 .borrow_mut()
269 265 .non_normal_entries_contains(HgPath::new(key.data(py)))
270 266 .map_err(|e| v2_error(py, e))
271 267 }
272 268
273 269 def non_normal_entries_display(&self) -> PyResult<PyString> {
274 270 let mut inner = self.inner(py).borrow_mut();
275 271 let paths = inner
276 272 .iter_non_normal_paths()
277 273 .collect::<Result<Vec<_>, _>>()
278 274 .map_err(|e| v2_error(py, e))?;
279 275 let formatted = format!("NonNormalEntries: {}", hg::utils::join_display(paths, ", "));
280 276 Ok(PyString::new(py, &formatted))
281 277 }
282 278
283 279 def non_normal_entries_remove(&self, key: PyObject) -> PyResult<PyObject> {
284 280 let key = key.extract::<PyBytes>(py)?;
285 281 let key = key.data(py);
286 282 let was_present = self
287 283 .inner(py)
288 284 .borrow_mut()
289 285 .non_normal_entries_remove(HgPath::new(key));
290 286 if !was_present {
291 287 let msg = String::from_utf8_lossy(key);
292 288 Err(PyErr::new::<exc::KeyError, _>(py, msg))
293 289 } else {
294 290 Ok(py.None())
295 291 }
296 292 }
297 293
298 294 def non_normal_entries_discard(&self, key: PyObject) -> PyResult<PyObject>
299 295 {
300 296 let key = key.extract::<PyBytes>(py)?;
301 297 self
302 298 .inner(py)
303 299 .borrow_mut()
304 300 .non_normal_entries_remove(HgPath::new(key.data(py)));
305 301 Ok(py.None())
306 302 }
307 303
308 304 def non_normal_entries_add(&self, key: PyObject) -> PyResult<PyObject> {
309 305 let key = key.extract::<PyBytes>(py)?;
310 306 self
311 307 .inner(py)
312 308 .borrow_mut()
313 309 .non_normal_entries_add(HgPath::new(key.data(py)));
314 310 Ok(py.None())
315 311 }
316 312
317 313 def non_normal_or_other_parent_paths(&self) -> PyResult<PyList> {
318 314 let mut inner = self.inner(py).borrow_mut();
319 315
320 316 let ret = PyList::new(py, &[]);
321 317 for filename in inner.non_normal_or_other_parent_paths() {
322 318 let filename = filename.map_err(|e| v2_error(py, e))?;
323 319 let as_pystring = PyBytes::new(py, filename.as_bytes());
324 320 ret.append(py, as_pystring.into_object());
325 321 }
326 322 Ok(ret)
327 323 }
328 324
329 325 def non_normal_entries_iter(&self) -> PyResult<NonNormalEntriesIterator> {
330 326 // Make sure the sets are defined before we no longer have a mutable
331 327 // reference to the dmap.
332 328 self.inner(py)
333 329 .borrow_mut()
334 330 .set_non_normal_other_parent_entries(false);
335 331
336 332 let leaked_ref = self.inner(py).leak_immutable();
337 333
338 334 NonNormalEntriesIterator::from_inner(py, unsafe {
339 335 leaked_ref.map(py, |o| {
340 336 o.iter_non_normal_paths_panic()
341 337 })
342 338 })
343 339 }
344 340
345 341 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
346 342 let d = d.extract::<PyBytes>(py)?;
347 343 Ok(self.inner(py).borrow_mut()
348 344 .has_tracked_dir(HgPath::new(d.data(py)))
349 345 .map_err(|e| {
350 346 PyErr::new::<exc::ValueError, _>(py, e.to_string())
351 347 })?
352 348 .to_py_object(py))
353 349 }
354 350
355 351 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
356 352 let d = d.extract::<PyBytes>(py)?;
357 353 Ok(self.inner(py).borrow_mut()
358 354 .has_dir(HgPath::new(d.data(py)))
359 355 .map_err(|e| {
360 356 PyErr::new::<exc::ValueError, _>(py, e.to_string())
361 357 })?
362 358 .to_py_object(py))
363 359 }
364 360
365 361 def write_v1(
366 362 &self,
367 363 p1: PyObject,
368 364 p2: PyObject,
369 365 now: PyObject
370 366 ) -> PyResult<PyBytes> {
371 367 let now = Timestamp(now.extract(py)?);
372 368
373 369 let mut inner = self.inner(py).borrow_mut();
374 370 let parents = DirstateParents {
375 371 p1: extract_node_id(py, &p1)?,
376 372 p2: extract_node_id(py, &p2)?,
377 373 };
378 374 let result = inner.pack_v1(parents, now);
379 375 match result {
380 376 Ok(packed) => Ok(PyBytes::new(py, &packed)),
381 377 Err(_) => Err(PyErr::new::<exc::OSError, _>(
382 378 py,
383 379 "Dirstate error".to_string(),
384 380 )),
385 381 }
386 382 }
387 383
388 384 /// Returns new data together with whether that data should be appended to
389 385 /// the existing data file whose content is at `self.on_disk` (True),
390 386 /// instead of written to a new data file (False).
391 387 def write_v2(
392 388 &self,
393 389 now: PyObject,
394 390 can_append: bool,
395 391 ) -> PyResult<PyObject> {
396 392 let now = Timestamp(now.extract(py)?);
397 393
398 394 let mut inner = self.inner(py).borrow_mut();
399 395 let result = inner.pack_v2(now, can_append);
400 396 match result {
401 397 Ok((packed, tree_metadata, append)) => {
402 398 let packed = PyBytes::new(py, &packed);
403 399 let tree_metadata = PyBytes::new(py, &tree_metadata);
404 400 let tuple = (packed, tree_metadata, append);
405 401 Ok(tuple.to_py_object(py).into_object())
406 402 },
407 403 Err(_) => Err(PyErr::new::<exc::OSError, _>(
408 404 py,
409 405 "Dirstate error".to_string(),
410 406 )),
411 407 }
412 408 }
413 409
414 410 def filefoldmapasdict(&self) -> PyResult<PyDict> {
415 411 let dict = PyDict::new(py);
416 412 for item in self.inner(py).borrow_mut().iter() {
417 413 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
418 414 if entry.state() != EntryState::Removed {
419 415 let key = normalize_case(path);
420 416 let value = path;
421 417 dict.set_item(
422 418 py,
423 419 PyBytes::new(py, key.as_bytes()).into_object(),
424 420 PyBytes::new(py, value.as_bytes()).into_object(),
425 421 )?;
426 422 }
427 423 }
428 424 Ok(dict)
429 425 }
430 426
431 427 def __len__(&self) -> PyResult<usize> {
432 428 Ok(self.inner(py).borrow().len())
433 429 }
434 430
435 431 def __contains__(&self, key: PyObject) -> PyResult<bool> {
436 432 let key = key.extract::<PyBytes>(py)?;
437 433 self.inner(py)
438 434 .borrow()
439 435 .contains_key(HgPath::new(key.data(py)))
440 436 .map_err(|e| v2_error(py, e))
441 437 }
442 438
443 439 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
444 440 let key = key.extract::<PyBytes>(py)?;
445 441 let key = HgPath::new(key.data(py));
446 442 match self
447 443 .inner(py)
448 444 .borrow()
449 445 .get(key)
450 446 .map_err(|e| v2_error(py, e))?
451 447 {
452 448 Some(entry) => {
453 449 Ok(DirstateItem::new_as_pyobject(py, entry)?)
454 450 },
455 451 None => Err(PyErr::new::<exc::KeyError, _>(
456 452 py,
457 453 String::from_utf8_lossy(key.as_bytes()),
458 454 )),
459 455 }
460 456 }
461 457
462 458 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
463 459 let leaked_ref = self.inner(py).leak_immutable();
464 460 DirstateMapKeysIterator::from_inner(
465 461 py,
466 462 unsafe { leaked_ref.map(py, |o| o.iter()) },
467 463 )
468 464 }
469 465
470 466 def items(&self) -> PyResult<DirstateMapItemsIterator> {
471 467 let leaked_ref = self.inner(py).leak_immutable();
472 468 DirstateMapItemsIterator::from_inner(
473 469 py,
474 470 unsafe { leaked_ref.map(py, |o| o.iter()) },
475 471 )
476 472 }
477 473
478 474 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
479 475 let leaked_ref = self.inner(py).leak_immutable();
480 476 DirstateMapKeysIterator::from_inner(
481 477 py,
482 478 unsafe { leaked_ref.map(py, |o| o.iter()) },
483 479 )
484 480 }
485 481
486 482 // TODO all copymap* methods, see docstring above
487 483 def copymapcopy(&self) -> PyResult<PyDict> {
488 484 let dict = PyDict::new(py);
489 485 for item in self.inner(py).borrow().copy_map_iter() {
490 486 let (key, value) = item.map_err(|e| v2_error(py, e))?;
491 487 dict.set_item(
492 488 py,
493 489 PyBytes::new(py, key.as_bytes()),
494 490 PyBytes::new(py, value.as_bytes()),
495 491 )?;
496 492 }
497 493 Ok(dict)
498 494 }
499 495
500 496 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
501 497 let key = key.extract::<PyBytes>(py)?;
502 498 match self
503 499 .inner(py)
504 500 .borrow()
505 501 .copy_map_get(HgPath::new(key.data(py)))
506 502 .map_err(|e| v2_error(py, e))?
507 503 {
508 504 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
509 505 None => Err(PyErr::new::<exc::KeyError, _>(
510 506 py,
511 507 String::from_utf8_lossy(key.data(py)),
512 508 )),
513 509 }
514 510 }
515 511 def copymap(&self) -> PyResult<CopyMap> {
516 512 CopyMap::from_inner(py, self.clone_ref(py))
517 513 }
518 514
519 515 def copymaplen(&self) -> PyResult<usize> {
520 516 Ok(self.inner(py).borrow().copy_map_len())
521 517 }
522 518 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
523 519 let key = key.extract::<PyBytes>(py)?;
524 520 self.inner(py)
525 521 .borrow()
526 522 .copy_map_contains_key(HgPath::new(key.data(py)))
527 523 .map_err(|e| v2_error(py, e))
528 524 }
529 525 def copymapget(
530 526 &self,
531 527 key: PyObject,
532 528 default: Option<PyObject>
533 529 ) -> PyResult<Option<PyObject>> {
534 530 let key = key.extract::<PyBytes>(py)?;
535 531 match self
536 532 .inner(py)
537 533 .borrow()
538 534 .copy_map_get(HgPath::new(key.data(py)))
539 535 .map_err(|e| v2_error(py, e))?
540 536 {
541 537 Some(copy) => Ok(Some(
542 538 PyBytes::new(py, copy.as_bytes()).into_object(),
543 539 )),
544 540 None => Ok(default),
545 541 }
546 542 }
547 543 def copymapsetitem(
548 544 &self,
549 545 key: PyObject,
550 546 value: PyObject
551 547 ) -> PyResult<PyObject> {
552 548 let key = key.extract::<PyBytes>(py)?;
553 549 let value = value.extract::<PyBytes>(py)?;
554 550 self.inner(py)
555 551 .borrow_mut()
556 552 .copy_map_insert(
557 553 HgPathBuf::from_bytes(key.data(py)),
558 554 HgPathBuf::from_bytes(value.data(py)),
559 555 )
560 556 .map_err(|e| v2_error(py, e))?;
561 557 Ok(py.None())
562 558 }
563 559 def copymappop(
564 560 &self,
565 561 key: PyObject,
566 562 default: Option<PyObject>
567 563 ) -> PyResult<Option<PyObject>> {
568 564 let key = key.extract::<PyBytes>(py)?;
569 565 match self
570 566 .inner(py)
571 567 .borrow_mut()
572 568 .copy_map_remove(HgPath::new(key.data(py)))
573 569 .map_err(|e| v2_error(py, e))?
574 570 {
575 571 Some(_) => Ok(None),
576 572 None => Ok(default),
577 573 }
578 574 }
579 575
580 576 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
581 577 let leaked_ref = self.inner(py).leak_immutable();
582 578 CopyMapKeysIterator::from_inner(
583 579 py,
584 580 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
585 581 )
586 582 }
587 583
588 584 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
589 585 let leaked_ref = self.inner(py).leak_immutable();
590 586 CopyMapItemsIterator::from_inner(
591 587 py,
592 588 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
593 589 )
594 590 }
595 591
596 592 def tracked_dirs(&self) -> PyResult<PyList> {
597 593 let dirs = PyList::new(py, &[]);
598 594 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
599 595 .map_err(|e |dirstate_error(py, e))?
600 596 {
601 597 let path = path.map_err(|e| v2_error(py, e))?;
602 598 let path = PyBytes::new(py, path.as_bytes());
603 599 dirs.append(py, path.into_object())
604 600 }
605 601 Ok(dirs)
606 602 }
607 603
608 604 def debug_iter(&self, all: bool) -> PyResult<PyList> {
609 605 let dirs = PyList::new(py, &[]);
610 606 for item in self.inner(py).borrow().debug_iter(all) {
611 607 let (path, (state, mode, size, mtime)) =
612 608 item.map_err(|e| v2_error(py, e))?;
613 609 let path = PyBytes::new(py, path.as_bytes());
614 610 let item = (path, state, mode, size, mtime);
615 611 dirs.append(py, item.to_py_object(py).into_object())
616 612 }
617 613 Ok(dirs)
618 614 }
619 615 });
620 616
621 617 impl DirstateMap {
622 618 pub fn get_inner_mut<'a>(
623 619 &'a self,
624 620 py: Python<'a>,
625 621 ) -> RefMut<'a, Box<dyn DirstateMapMethods + Send>> {
626 622 self.inner(py).borrow_mut()
627 623 }
628 624 fn translate_key(
629 625 py: Python,
630 626 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
631 627 ) -> PyResult<Option<PyBytes>> {
632 628 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
633 629 Ok(Some(PyBytes::new(py, f.as_bytes())))
634 630 }
635 631 fn translate_key_value(
636 632 py: Python,
637 633 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
638 634 ) -> PyResult<Option<(PyBytes, PyObject)>> {
639 635 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
640 636 Ok(Some((
641 637 PyBytes::new(py, f.as_bytes()),
642 638 DirstateItem::new_as_pyobject(py, entry)?,
643 639 )))
644 640 }
645 641 }
646 642
647 643 py_shared_iterator!(
648 644 DirstateMapKeysIterator,
649 645 UnsafePyLeaked<StateMapIter<'static>>,
650 646 DirstateMap::translate_key,
651 647 Option<PyBytes>
652 648 );
653 649
654 650 py_shared_iterator!(
655 651 DirstateMapItemsIterator,
656 652 UnsafePyLeaked<StateMapIter<'static>>,
657 653 DirstateMap::translate_key_value,
658 654 Option<(PyBytes, PyObject)>
659 655 );
660 656
661 657 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
662 658 let bytes = obj.extract::<PyBytes>(py)?;
663 659 match bytes.data(py).try_into() {
664 660 Ok(s) => Ok(s),
665 661 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
666 662 }
667 663 }
668 664
669 665 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
670 666 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
671 667 }
672 668
673 669 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
674 670 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
675 671 }
@@ -1,189 +1,193 b''
1 1 use cpython::exc;
2 2 use cpython::PyBytes;
3 3 use cpython::PyErr;
4 4 use cpython::PyNone;
5 5 use cpython::PyObject;
6 6 use cpython::PyResult;
7 7 use cpython::Python;
8 8 use cpython::PythonObject;
9 9 use hg::dirstate::entry::Flags;
10 10 use hg::dirstate::DirstateEntry;
11 11 use hg::dirstate::EntryState;
12 12 use std::cell::Cell;
13 13 use std::convert::TryFrom;
14 14
15 15 py_class!(pub class DirstateItem |py| {
16 16 data entry: Cell<DirstateEntry>;
17 17
18 18 def __new__(
19 19 _cls,
20 20 wc_tracked: bool = false,
21 21 p1_tracked: bool = false,
22 22 p2_tracked: bool = false,
23 23 merged: bool = false,
24 24 clean_p1: bool = false,
25 25 clean_p2: bool = false,
26 26 possibly_dirty: bool = false,
27 27 parentfiledata: Option<(i32, i32, i32)> = None,
28 28
29 29 ) -> PyResult<DirstateItem> {
30 30 let mut flags = Flags::empty();
31 31 flags.set(Flags::WDIR_TRACKED, wc_tracked);
32 32 flags.set(Flags::P1_TRACKED, p1_tracked);
33 33 flags.set(Flags::P2_TRACKED, p2_tracked);
34 34 flags.set(Flags::MERGED, merged);
35 35 flags.set(Flags::CLEAN_P1, clean_p1);
36 36 flags.set(Flags::CLEAN_P2, clean_p2);
37 37 flags.set(Flags::POSSIBLY_DIRTY, possibly_dirty);
38 38 let entry = DirstateEntry::new(flags, parentfiledata);
39 39 DirstateItem::create_instance(py, Cell::new(entry))
40 40 }
41 41
42 42 @property
43 43 def state(&self) -> PyResult<PyBytes> {
44 44 let state_byte: u8 = self.entry(py).get().state().into();
45 45 Ok(PyBytes::new(py, &[state_byte]))
46 46 }
47 47
48 48 @property
49 49 def mode(&self) -> PyResult<i32> {
50 50 Ok(self.entry(py).get().mode())
51 51 }
52 52
53 53 @property
54 54 def size(&self) -> PyResult<i32> {
55 55 Ok(self.entry(py).get().size())
56 56 }
57 57
58 58 @property
59 59 def mtime(&self) -> PyResult<i32> {
60 60 Ok(self.entry(py).get().mtime())
61 61 }
62 62
63 63 @property
64 64 def tracked(&self) -> PyResult<bool> {
65 65 Ok(self.entry(py).get().tracked())
66 66 }
67 67
68 68 @property
69 69 def added(&self) -> PyResult<bool> {
70 70 Ok(self.entry(py).get().added())
71 71 }
72 72
73 73 @property
74 74 def merged(&self) -> PyResult<bool> {
75 75 Ok(self.entry(py).get().merged())
76 76 }
77 77
78 78 @property
79 79 def removed(&self) -> PyResult<bool> {
80 80 Ok(self.entry(py).get().removed())
81 81 }
82 82
83 83 @property
84 84 def from_p2(&self) -> PyResult<bool> {
85 85 Ok(self.entry(py).get().from_p2())
86 86 }
87 87
88 88 @property
89 89 def merged_removed(&self) -> PyResult<bool> {
90 90 Ok(self.entry(py).get().merged_removed())
91 91 }
92 92
93 93 @property
94 94 def from_p2_removed(&self) -> PyResult<bool> {
95 95 Ok(self.entry(py).get().from_p2_removed())
96 96 }
97 97
98 98 @property
99 99 def dm_nonnormal(&self) -> PyResult<bool> {
100 100 Ok(self.entry(py).get().is_non_normal())
101 101 }
102 102
103 103 @property
104 104 def dm_otherparent(&self) -> PyResult<bool> {
105 105 Ok(self.entry(py).get().is_from_other_parent())
106 106 }
107 107
108 108 def v1_state(&self) -> PyResult<PyBytes> {
109 109 let (state, _mode, _size, _mtime) = self.entry(py).get().v1_data();
110 110 let state_byte: u8 = state.into();
111 111 Ok(PyBytes::new(py, &[state_byte]))
112 112 }
113 113
114 114 def v1_mode(&self) -> PyResult<i32> {
115 115 let (_state, mode, _size, _mtime) = self.entry(py).get().v1_data();
116 116 Ok(mode)
117 117 }
118 118
119 119 def v1_size(&self) -> PyResult<i32> {
120 120 let (_state, _mode, size, _mtime) = self.entry(py).get().v1_data();
121 121 Ok(size)
122 122 }
123 123
124 124 def v1_mtime(&self) -> PyResult<i32> {
125 125 let (_state, _mode, _size, mtime) = self.entry(py).get().v1_data();
126 126 Ok(mtime)
127 127 }
128 128
129 129 def need_delay(&self, now: i32) -> PyResult<bool> {
130 130 Ok(self.entry(py).get().mtime_is_ambiguous(now))
131 131 }
132 132
133 133 @classmethod
134 134 def from_v1_data(
135 135 _cls,
136 136 state: PyBytes,
137 137 mode: i32,
138 138 size: i32,
139 139 mtime: i32,
140 140 ) -> PyResult<Self> {
141 141 let state = <[u8; 1]>::try_from(state.data(py))
142 142 .ok()
143 143 .and_then(|state| EntryState::try_from(state[0]).ok())
144 144 .ok_or_else(|| PyErr::new::<exc::ValueError, _>(py, "invalid state"))?;
145 145 let entry = DirstateEntry::from_v1_data(state, mode, size, mtime);
146 146 DirstateItem::create_instance(py, Cell::new(entry))
147 147 }
148 148
149 149 def set_clean(
150 150 &self,
151 151 mode: i32,
152 152 size: i32,
153 153 mtime: i32,
154 154 ) -> PyResult<PyNone> {
155 155 self.update(py, |entry| entry.set_clean(mode, size, mtime));
156 156 Ok(PyNone)
157 157 }
158 158
159 159 def set_possibly_dirty(&self) -> PyResult<PyNone> {
160 160 self.update(py, |entry| entry.set_possibly_dirty());
161 161 Ok(PyNone)
162 162 }
163 163
164 164 def set_tracked(&self) -> PyResult<PyNone> {
165 165 self.update(py, |entry| entry.set_tracked());
166 166 Ok(PyNone)
167 167 }
168 168
169 169 def set_untracked(&self) -> PyResult<PyNone> {
170 170 self.update(py, |entry| entry.set_untracked());
171 171 Ok(PyNone)
172 172 }
173 173 });
174 174
175 175 impl DirstateItem {
176 176 pub fn new_as_pyobject(
177 177 py: Python<'_>,
178 178 entry: DirstateEntry,
179 179 ) -> PyResult<PyObject> {
180 180 Ok(DirstateItem::create_instance(py, Cell::new(entry))?.into_object())
181 181 }
182 182
183 pub fn get_entry(&self, py: Python<'_>) -> DirstateEntry {
184 self.entry(py).get()
185 }
186
183 187 // TODO: Use https://doc.rust-lang.org/std/cell/struct.Cell.html#method.update instead when it’s stable
184 188 pub fn update(&self, py: Python<'_>, f: impl FnOnce(&mut DirstateEntry)) {
185 189 let mut entry = self.entry(py).get();
186 190 f(&mut entry);
187 191 self.entry(py).set(entry)
188 192 }
189 193 }
General Comments 0
You need to be logged in to leave comments. Login now