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