##// END OF EJS Templates
rust-dirstatemap: temporarily use `from_v1_data` in `addfile`...
marmoute -
r48705:226c7dbe default
parent child Browse files
Show More
@@ -1,916 +1,916 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 DirstateItem = parsers.DirstateItem
31 31
32 32
33 33 # a special value used internally for `size` if the file come from the other parent
34 34 FROM_P2 = -2
35 35
36 36 # a special value used internally for `size` if the file is modified/merged/added
37 37 NONNORMAL = -1
38 38
39 39 # a special value used internally for `time` if the time is ambigeous
40 40 AMBIGUOUS_TIME = -1
41 41
42 42 rangemask = 0x7FFFFFFF
43 43
44 44
45 45 class dirstatemap(object):
46 46 """Map encapsulating the dirstate's contents.
47 47
48 48 The dirstate contains the following state:
49 49
50 50 - `identity` is the identity of the dirstate file, which can be used to
51 51 detect when changes have occurred to the dirstate file.
52 52
53 53 - `parents` is a pair containing the parents of the working copy. The
54 54 parents are updated by calling `setparents`.
55 55
56 56 - the state map maps filenames to tuples of (state, mode, size, mtime),
57 57 where state is a single character representing 'normal', 'added',
58 58 'removed', or 'merged'. It is read by treating the dirstate as a
59 59 dict. File state is updated by calling the `addfile`, `removefile` and
60 60 `dropfile` methods.
61 61
62 62 - `copymap` maps destination filenames to their source filename.
63 63
64 64 The dirstate also provides the following views onto the state:
65 65
66 66 - `nonnormalset` is a set of the filenames that have state other
67 67 than 'normal', or are normal but have an mtime of -1 ('normallookup').
68 68
69 69 - `otherparentset` is a set of the filenames that are marked as coming
70 70 from the second parent when the dirstate is currently being merged.
71 71
72 72 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
73 73 form that they appear as in the dirstate.
74 74
75 75 - `dirfoldmap` is a dict mapping normalized directory names to the
76 76 denormalized form that they appear as in the dirstate.
77 77 """
78 78
79 79 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
80 80 self._ui = ui
81 81 self._opener = opener
82 82 self._root = root
83 83 self._filename = b'dirstate'
84 84 self._nodelen = 20
85 85 self._nodeconstants = nodeconstants
86 86 assert (
87 87 not use_dirstate_v2
88 88 ), "should have detected unsupported requirement"
89 89
90 90 self._parents = None
91 91 self._dirtyparents = False
92 92
93 93 # for consistent view between _pl() and _read() invocations
94 94 self._pendingmode = None
95 95
96 96 @propertycache
97 97 def _map(self):
98 98 self._map = {}
99 99 self.read()
100 100 return self._map
101 101
102 102 @propertycache
103 103 def copymap(self):
104 104 self.copymap = {}
105 105 self._map
106 106 return self.copymap
107 107
108 108 def clear(self):
109 109 self._map.clear()
110 110 self.copymap.clear()
111 111 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
112 112 util.clearcachedproperty(self, b"_dirs")
113 113 util.clearcachedproperty(self, b"_alldirs")
114 114 util.clearcachedproperty(self, b"filefoldmap")
115 115 util.clearcachedproperty(self, b"dirfoldmap")
116 116 util.clearcachedproperty(self, b"nonnormalset")
117 117 util.clearcachedproperty(self, b"otherparentset")
118 118
119 119 def items(self):
120 120 return pycompat.iteritems(self._map)
121 121
122 122 # forward for python2,3 compat
123 123 iteritems = items
124 124
125 125 debug_iter = items
126 126
127 127 def __len__(self):
128 128 return len(self._map)
129 129
130 130 def __iter__(self):
131 131 return iter(self._map)
132 132
133 133 def get(self, key, default=None):
134 134 return self._map.get(key, default)
135 135
136 136 def __contains__(self, key):
137 137 return key in self._map
138 138
139 139 def __getitem__(self, key):
140 140 return self._map[key]
141 141
142 142 def keys(self):
143 143 return self._map.keys()
144 144
145 145 def preload(self):
146 146 """Loads the underlying data, if it's not already loaded"""
147 147 self._map
148 148
149 149 def _dirs_incr(self, filename, old_entry=None):
150 150 """incremente the dirstate counter if applicable"""
151 151 if (
152 152 old_entry is None or old_entry.removed
153 153 ) and "_dirs" in self.__dict__:
154 154 self._dirs.addpath(filename)
155 155 if old_entry is None and "_alldirs" in self.__dict__:
156 156 self._alldirs.addpath(filename)
157 157
158 158 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
159 159 """decremente the dirstate counter if applicable"""
160 160 if old_entry is not None:
161 161 if "_dirs" in self.__dict__ and not old_entry.removed:
162 162 self._dirs.delpath(filename)
163 163 if "_alldirs" in self.__dict__ and not remove_variant:
164 164 self._alldirs.delpath(filename)
165 165 elif remove_variant and "_alldirs" in self.__dict__:
166 166 self._alldirs.addpath(filename)
167 167 if "filefoldmap" in self.__dict__:
168 168 normed = util.normcase(filename)
169 169 self.filefoldmap.pop(normed, None)
170 170
171 171 def set_possibly_dirty(self, filename):
172 172 """record that the current state of the file on disk is unknown"""
173 173 self[filename].set_possibly_dirty()
174 174
175 175 def addfile(
176 176 self,
177 177 f,
178 178 mode=0,
179 179 size=None,
180 180 mtime=None,
181 181 added=False,
182 182 merged=False,
183 183 from_p2=False,
184 184 possibly_dirty=False,
185 185 ):
186 186 """Add a tracked file to the dirstate."""
187 187 if added:
188 188 assert not merged
189 189 assert not possibly_dirty
190 190 assert not from_p2
191 191 state = b'a'
192 192 size = NONNORMAL
193 193 mtime = AMBIGUOUS_TIME
194 194 elif merged:
195 195 assert not possibly_dirty
196 196 assert not from_p2
197 197 state = b'm'
198 198 size = FROM_P2
199 199 mtime = AMBIGUOUS_TIME
200 200 elif from_p2:
201 201 assert not possibly_dirty
202 202 state = b'n'
203 203 size = FROM_P2
204 204 mtime = AMBIGUOUS_TIME
205 205 elif possibly_dirty:
206 206 state = b'n'
207 207 size = NONNORMAL
208 208 mtime = AMBIGUOUS_TIME
209 209 else:
210 210 assert size != FROM_P2
211 211 assert size != NONNORMAL
212 212 assert size is not None
213 213 assert mtime is not None
214 214
215 215 state = b'n'
216 216 size = size & rangemask
217 217 mtime = mtime & rangemask
218 218 assert state is not None
219 219 assert size is not None
220 220 assert mtime is not None
221 221 old_entry = self.get(f)
222 222 self._dirs_incr(f, old_entry)
223 223 e = self._map[f] = DirstateItem.from_v1_data(state, mode, size, mtime)
224 224 if e.dm_nonnormal:
225 225 self.nonnormalset.add(f)
226 226 if e.dm_otherparent:
227 227 self.otherparentset.add(f)
228 228
229 229 def reset_state(
230 230 self,
231 231 filename,
232 232 wc_tracked,
233 233 p1_tracked,
234 234 p2_tracked=False,
235 235 merged=False,
236 236 clean_p1=False,
237 237 clean_p2=False,
238 238 possibly_dirty=False,
239 239 parentfiledata=None,
240 240 ):
241 241 """Set a entry to a given state, diregarding all previous state
242 242
243 243 This is to be used by the part of the dirstate API dedicated to
244 244 adjusting the dirstate after a update/merge.
245 245
246 246 note: calling this might result to no entry existing at all if the
247 247 dirstate map does not see any point at having one for this file
248 248 anymore.
249 249 """
250 250 if merged and (clean_p1 or clean_p2):
251 251 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
252 252 raise error.ProgrammingError(msg)
253 253 # copy information are now outdated
254 254 # (maybe new information should be in directly passed to this function)
255 255 self.copymap.pop(filename, None)
256 256
257 257 if not (p1_tracked or p2_tracked or wc_tracked):
258 258 self.dropfile(filename)
259 259 elif merged:
260 260 # XXX might be merged and removed ?
261 261 entry = self.get(filename)
262 262 if entry is not None and entry.tracked:
263 263 # XXX mostly replicate dirstate.other parent. We should get
264 264 # the higher layer to pass us more reliable data where `merged`
265 265 # actually mean merged. Dropping the else clause will show
266 266 # failure in `test-graft.t`
267 267 self.addfile(filename, merged=True)
268 268 else:
269 269 self.addfile(filename, from_p2=True)
270 270 elif not (p1_tracked or p2_tracked) and wc_tracked:
271 271 self.addfile(filename, added=True, possibly_dirty=possibly_dirty)
272 272 elif (p1_tracked or p2_tracked) and not wc_tracked:
273 273 # XXX might be merged and removed ?
274 274 old_entry = self._map.get(filename)
275 275 self._dirs_decr(filename, old_entry=old_entry, remove_variant=True)
276 276 self._map[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
277 277 self.nonnormalset.add(filename)
278 278 elif clean_p2 and wc_tracked:
279 279 if p1_tracked or self.get(filename) is not None:
280 280 # XXX the `self.get` call is catching some case in
281 281 # `test-merge-remove.t` where the file is tracked in p1, the
282 282 # p1_tracked argument is False.
283 283 #
284 284 # In addition, this seems to be a case where the file is marked
285 285 # as merged without actually being the result of a merge
286 286 # action. So thing are not ideal here.
287 287 self.addfile(filename, merged=True)
288 288 else:
289 289 self.addfile(filename, from_p2=True)
290 290 elif not p1_tracked and p2_tracked and wc_tracked:
291 291 self.addfile(filename, from_p2=True, possibly_dirty=possibly_dirty)
292 292 elif possibly_dirty:
293 293 self.addfile(filename, possibly_dirty=possibly_dirty)
294 294 elif wc_tracked:
295 295 # this is a "normal" file
296 296 if parentfiledata is None:
297 297 msg = b'failed to pass parentfiledata for a normal file: %s'
298 298 msg %= filename
299 299 raise error.ProgrammingError(msg)
300 300 mode, size, mtime = parentfiledata
301 301 self.addfile(filename, mode=mode, size=size, mtime=mtime)
302 302 self.nonnormalset.discard(filename)
303 303 else:
304 304 assert False, 'unreachable'
305 305
306 306 def set_untracked(self, f):
307 307 """Mark a file as no longer tracked in the dirstate map"""
308 308 entry = self[f]
309 309 self._dirs_decr(f, old_entry=entry, remove_variant=True)
310 310 if entry.from_p2:
311 311 self.otherparentset.add(f)
312 312 elif not entry.merged:
313 313 self.copymap.pop(f, None)
314 314 entry.set_untracked()
315 315 self.nonnormalset.add(f)
316 316
317 317 def dropfile(self, f):
318 318 """
319 319 Remove a file from the dirstate. Returns True if the file was
320 320 previously recorded.
321 321 """
322 322 old_entry = self._map.pop(f, None)
323 323 self._dirs_decr(f, old_entry=old_entry)
324 324 self.nonnormalset.discard(f)
325 325 return old_entry is not None
326 326
327 327 def clearambiguoustimes(self, files, now):
328 328 for f in files:
329 329 e = self.get(f)
330 330 if e is not None and e.need_delay(now):
331 331 e.set_possibly_dirty()
332 332 self.nonnormalset.add(f)
333 333
334 334 def nonnormalentries(self):
335 335 '''Compute the nonnormal dirstate entries from the dmap'''
336 336 try:
337 337 return parsers.nonnormalotherparententries(self._map)
338 338 except AttributeError:
339 339 nonnorm = set()
340 340 otherparent = set()
341 341 for fname, e in pycompat.iteritems(self._map):
342 342 if e.dm_nonnormal:
343 343 nonnorm.add(fname)
344 344 if e.from_p2:
345 345 otherparent.add(fname)
346 346 return nonnorm, otherparent
347 347
348 348 @propertycache
349 349 def filefoldmap(self):
350 350 """Returns a dictionary mapping normalized case paths to their
351 351 non-normalized versions.
352 352 """
353 353 try:
354 354 makefilefoldmap = parsers.make_file_foldmap
355 355 except AttributeError:
356 356 pass
357 357 else:
358 358 return makefilefoldmap(
359 359 self._map, util.normcasespec, util.normcasefallback
360 360 )
361 361
362 362 f = {}
363 363 normcase = util.normcase
364 364 for name, s in pycompat.iteritems(self._map):
365 365 if not s.removed:
366 366 f[normcase(name)] = name
367 367 f[b'.'] = b'.' # prevents useless util.fspath() invocation
368 368 return f
369 369
370 370 def hastrackeddir(self, d):
371 371 """
372 372 Returns True if the dirstate contains a tracked (not removed) file
373 373 in this directory.
374 374 """
375 375 return d in self._dirs
376 376
377 377 def hasdir(self, d):
378 378 """
379 379 Returns True if the dirstate contains a file (tracked or removed)
380 380 in this directory.
381 381 """
382 382 return d in self._alldirs
383 383
384 384 @propertycache
385 385 def _dirs(self):
386 386 return pathutil.dirs(self._map, b'r')
387 387
388 388 @propertycache
389 389 def _alldirs(self):
390 390 return pathutil.dirs(self._map)
391 391
392 392 def _opendirstatefile(self):
393 393 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
394 394 if self._pendingmode is not None and self._pendingmode != mode:
395 395 fp.close()
396 396 raise error.Abort(
397 397 _(b'working directory state may be changed parallelly')
398 398 )
399 399 self._pendingmode = mode
400 400 return fp
401 401
402 402 def parents(self):
403 403 if not self._parents:
404 404 try:
405 405 fp = self._opendirstatefile()
406 406 st = fp.read(2 * self._nodelen)
407 407 fp.close()
408 408 except IOError as err:
409 409 if err.errno != errno.ENOENT:
410 410 raise
411 411 # File doesn't exist, so the current state is empty
412 412 st = b''
413 413
414 414 l = len(st)
415 415 if l == self._nodelen * 2:
416 416 self._parents = (
417 417 st[: self._nodelen],
418 418 st[self._nodelen : 2 * self._nodelen],
419 419 )
420 420 elif l == 0:
421 421 self._parents = (
422 422 self._nodeconstants.nullid,
423 423 self._nodeconstants.nullid,
424 424 )
425 425 else:
426 426 raise error.Abort(
427 427 _(b'working directory state appears damaged!')
428 428 )
429 429
430 430 return self._parents
431 431
432 432 def setparents(self, p1, p2):
433 433 self._parents = (p1, p2)
434 434 self._dirtyparents = True
435 435
436 436 def read(self):
437 437 # ignore HG_PENDING because identity is used only for writing
438 438 self.identity = util.filestat.frompath(
439 439 self._opener.join(self._filename)
440 440 )
441 441
442 442 try:
443 443 fp = self._opendirstatefile()
444 444 try:
445 445 st = fp.read()
446 446 finally:
447 447 fp.close()
448 448 except IOError as err:
449 449 if err.errno != errno.ENOENT:
450 450 raise
451 451 return
452 452 if not st:
453 453 return
454 454
455 455 if util.safehasattr(parsers, b'dict_new_presized'):
456 456 # Make an estimate of the number of files in the dirstate based on
457 457 # its size. This trades wasting some memory for avoiding costly
458 458 # resizes. Each entry have a prefix of 17 bytes followed by one or
459 459 # two path names. Studies on various large-scale real-world repositories
460 460 # found 54 bytes a reasonable upper limit for the average path names.
461 461 # Copy entries are ignored for the sake of this estimate.
462 462 self._map = parsers.dict_new_presized(len(st) // 71)
463 463
464 464 # Python's garbage collector triggers a GC each time a certain number
465 465 # of container objects (the number being defined by
466 466 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
467 467 # for each file in the dirstate. The C version then immediately marks
468 468 # them as not to be tracked by the collector. However, this has no
469 469 # effect on when GCs are triggered, only on what objects the GC looks
470 470 # into. This means that O(number of files) GCs are unavoidable.
471 471 # Depending on when in the process's lifetime the dirstate is parsed,
472 472 # this can get very expensive. As a workaround, disable GC while
473 473 # parsing the dirstate.
474 474 #
475 475 # (we cannot decorate the function directly since it is in a C module)
476 476 parse_dirstate = util.nogc(parsers.parse_dirstate)
477 477 p = parse_dirstate(self._map, self.copymap, st)
478 478 if not self._dirtyparents:
479 479 self.setparents(*p)
480 480
481 481 # Avoid excess attribute lookups by fast pathing certain checks
482 482 self.__contains__ = self._map.__contains__
483 483 self.__getitem__ = self._map.__getitem__
484 484 self.get = self._map.get
485 485
486 486 def write(self, _tr, st, now):
487 487 st.write(
488 488 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
489 489 )
490 490 st.close()
491 491 self._dirtyparents = False
492 492 self.nonnormalset, self.otherparentset = self.nonnormalentries()
493 493
494 494 @propertycache
495 495 def nonnormalset(self):
496 496 nonnorm, otherparents = self.nonnormalentries()
497 497 self.otherparentset = otherparents
498 498 return nonnorm
499 499
500 500 @propertycache
501 501 def otherparentset(self):
502 502 nonnorm, otherparents = self.nonnormalentries()
503 503 self.nonnormalset = nonnorm
504 504 return otherparents
505 505
506 506 def non_normal_or_other_parent_paths(self):
507 507 return self.nonnormalset.union(self.otherparentset)
508 508
509 509 @propertycache
510 510 def identity(self):
511 511 self._map
512 512 return self.identity
513 513
514 514 @propertycache
515 515 def dirfoldmap(self):
516 516 f = {}
517 517 normcase = util.normcase
518 518 for name in self._dirs:
519 519 f[normcase(name)] = name
520 520 return f
521 521
522 522
523 523 if rustmod is not None:
524 524
525 525 class dirstatemap(object):
526 526 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
527 527 self._use_dirstate_v2 = use_dirstate_v2
528 528 self._nodeconstants = nodeconstants
529 529 self._ui = ui
530 530 self._opener = opener
531 531 self._root = root
532 532 self._filename = b'dirstate'
533 533 self._nodelen = 20 # Also update Rust code when changing this!
534 534 self._parents = None
535 535 self._dirtyparents = False
536 536 self._docket = None
537 537
538 538 # for consistent view between _pl() and _read() invocations
539 539 self._pendingmode = None
540 540
541 541 self._use_dirstate_tree = self._ui.configbool(
542 542 b"experimental",
543 543 b"dirstate-tree.in-memory",
544 544 False,
545 545 )
546 546
547 547 def addfile(
548 548 self,
549 549 f,
550 550 mode=0,
551 551 size=None,
552 552 mtime=None,
553 553 added=False,
554 554 merged=False,
555 555 from_p2=False,
556 556 possibly_dirty=False,
557 557 ):
558 558 return self._rustmap.addfile(
559 559 f,
560 560 mode,
561 561 size,
562 562 mtime,
563 563 added,
564 564 merged,
565 565 from_p2,
566 566 possibly_dirty,
567 567 )
568 568
569 569 def reset_state(
570 570 self,
571 571 filename,
572 572 wc_tracked,
573 573 p1_tracked,
574 574 p2_tracked=False,
575 575 merged=False,
576 576 clean_p1=False,
577 577 clean_p2=False,
578 578 possibly_dirty=False,
579 579 parentfiledata=None,
580 580 ):
581 581 """Set a entry to a given state, disregarding all previous state
582 582
583 583 This is to be used by the part of the dirstate API dedicated to
584 584 adjusting the dirstate after a update/merge.
585 585
586 586 note: calling this might result to no entry existing at all if the
587 587 dirstate map does not see any point at having one for this file
588 588 anymore.
589 589 """
590 590 if merged and (clean_p1 or clean_p2):
591 591 msg = (
592 592 b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
593 593 )
594 594 raise error.ProgrammingError(msg)
595 595 # copy information are now outdated
596 596 # (maybe new information should be in directly passed to this function)
597 597 self.copymap.pop(filename, None)
598 598
599 599 if not (p1_tracked or p2_tracked or wc_tracked):
600 600 self.dropfile(filename)
601 601 elif merged:
602 602 # XXX might be merged and removed ?
603 603 entry = self.get(filename)
604 604 if entry is not None and entry.tracked:
605 605 # XXX mostly replicate dirstate.other parent. We should get
606 606 # the higher layer to pass us more reliable data where `merged`
607 607 # actually mean merged. Dropping the else clause will show
608 608 # failure in `test-graft.t`
609 609 self.addfile(filename, merged=True)
610 610 else:
611 611 self.addfile(filename, from_p2=True)
612 612 elif not (p1_tracked or p2_tracked) and wc_tracked:
613 613 self.addfile(
614 614 filename, added=True, possibly_dirty=possibly_dirty
615 615 )
616 616 elif (p1_tracked or p2_tracked) and not wc_tracked:
617 617 # XXX might be merged and removed ?
618 self[filename] = DirstateItem(b'r', 0, 0, 0)
618 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
619 619 self.nonnormalset.add(filename)
620 620 elif clean_p2 and wc_tracked:
621 621 if p1_tracked or self.get(filename) is not None:
622 622 # XXX the `self.get` call is catching some case in
623 623 # `test-merge-remove.t` where the file is tracked in p1, the
624 624 # p1_tracked argument is False.
625 625 #
626 626 # In addition, this seems to be a case where the file is marked
627 627 # as merged without actually being the result of a merge
628 628 # action. So thing are not ideal here.
629 629 self.addfile(filename, merged=True)
630 630 else:
631 631 self.addfile(filename, from_p2=True)
632 632 elif not p1_tracked and p2_tracked and wc_tracked:
633 633 self.addfile(
634 634 filename, from_p2=True, possibly_dirty=possibly_dirty
635 635 )
636 636 elif possibly_dirty:
637 637 self.addfile(filename, possibly_dirty=possibly_dirty)
638 638 elif wc_tracked:
639 639 # this is a "normal" file
640 640 if parentfiledata is None:
641 641 msg = b'failed to pass parentfiledata for a normal file: %s'
642 642 msg %= filename
643 643 raise error.ProgrammingError(msg)
644 644 mode, size, mtime = parentfiledata
645 645 self.addfile(filename, mode=mode, size=size, mtime=mtime)
646 646 self.nonnormalset.discard(filename)
647 647 else:
648 648 assert False, 'unreachable'
649 649
650 650 def set_untracked(self, f):
651 651 """Mark a file as no longer tracked in the dirstate map"""
652 652 # in merge is only trigger more logic, so it "fine" to pass it.
653 653 #
654 654 # the inner rust dirstate map code need to be adjusted once the API
655 655 # for dirstate/dirstatemap/DirstateItem is a bit more settled
656 656 self._rustmap.removefile(f, in_merge=True)
657 657
658 658 def removefile(self, *args, **kwargs):
659 659 return self._rustmap.removefile(*args, **kwargs)
660 660
661 661 def dropfile(self, *args, **kwargs):
662 662 return self._rustmap.dropfile(*args, **kwargs)
663 663
664 664 def clearambiguoustimes(self, *args, **kwargs):
665 665 return self._rustmap.clearambiguoustimes(*args, **kwargs)
666 666
667 667 def nonnormalentries(self):
668 668 return self._rustmap.nonnormalentries()
669 669
670 670 def get(self, *args, **kwargs):
671 671 return self._rustmap.get(*args, **kwargs)
672 672
673 673 @property
674 674 def copymap(self):
675 675 return self._rustmap.copymap()
676 676
677 677 def directories(self):
678 678 return self._rustmap.directories()
679 679
680 680 def debug_iter(self):
681 681 return self._rustmap.debug_iter()
682 682
683 683 def preload(self):
684 684 self._rustmap
685 685
686 686 def clear(self):
687 687 self._rustmap.clear()
688 688 self.setparents(
689 689 self._nodeconstants.nullid, self._nodeconstants.nullid
690 690 )
691 691 util.clearcachedproperty(self, b"_dirs")
692 692 util.clearcachedproperty(self, b"_alldirs")
693 693 util.clearcachedproperty(self, b"dirfoldmap")
694 694
695 695 def items(self):
696 696 return self._rustmap.items()
697 697
698 698 def keys(self):
699 699 return iter(self._rustmap)
700 700
701 701 def __contains__(self, key):
702 702 return key in self._rustmap
703 703
704 704 def __getitem__(self, item):
705 705 return self._rustmap[item]
706 706
707 707 def __len__(self):
708 708 return len(self._rustmap)
709 709
710 710 def __iter__(self):
711 711 return iter(self._rustmap)
712 712
713 713 # forward for python2,3 compat
714 714 iteritems = items
715 715
716 716 def _opendirstatefile(self):
717 717 fp, mode = txnutil.trypending(
718 718 self._root, self._opener, self._filename
719 719 )
720 720 if self._pendingmode is not None and self._pendingmode != mode:
721 721 fp.close()
722 722 raise error.Abort(
723 723 _(b'working directory state may be changed parallelly')
724 724 )
725 725 self._pendingmode = mode
726 726 return fp
727 727
728 728 def _readdirstatefile(self, size=-1):
729 729 try:
730 730 with self._opendirstatefile() as fp:
731 731 return fp.read(size)
732 732 except IOError as err:
733 733 if err.errno != errno.ENOENT:
734 734 raise
735 735 # File doesn't exist, so the current state is empty
736 736 return b''
737 737
738 738 def setparents(self, p1, p2):
739 739 self._parents = (p1, p2)
740 740 self._dirtyparents = True
741 741
742 742 def parents(self):
743 743 if not self._parents:
744 744 if self._use_dirstate_v2:
745 745 self._parents = self.docket.parents
746 746 else:
747 747 read_len = self._nodelen * 2
748 748 st = self._readdirstatefile(read_len)
749 749 l = len(st)
750 750 if l == read_len:
751 751 self._parents = (
752 752 st[: self._nodelen],
753 753 st[self._nodelen : 2 * self._nodelen],
754 754 )
755 755 elif l == 0:
756 756 self._parents = (
757 757 self._nodeconstants.nullid,
758 758 self._nodeconstants.nullid,
759 759 )
760 760 else:
761 761 raise error.Abort(
762 762 _(b'working directory state appears damaged!')
763 763 )
764 764
765 765 return self._parents
766 766
767 767 @property
768 768 def docket(self):
769 769 if not self._docket:
770 770 if not self._use_dirstate_v2:
771 771 raise error.ProgrammingError(
772 772 b'dirstate only has a docket in v2 format'
773 773 )
774 774 self._docket = docketmod.DirstateDocket.parse(
775 775 self._readdirstatefile(), self._nodeconstants
776 776 )
777 777 return self._docket
778 778
779 779 @propertycache
780 780 def _rustmap(self):
781 781 """
782 782 Fills the Dirstatemap when called.
783 783 """
784 784 # ignore HG_PENDING because identity is used only for writing
785 785 self.identity = util.filestat.frompath(
786 786 self._opener.join(self._filename)
787 787 )
788 788
789 789 if self._use_dirstate_v2:
790 790 if self.docket.uuid:
791 791 # TODO: use mmap when possible
792 792 data = self._opener.read(self.docket.data_filename())
793 793 else:
794 794 data = b''
795 795 self._rustmap = rustmod.DirstateMap.new_v2(
796 796 data, self.docket.data_size, self.docket.tree_metadata
797 797 )
798 798 parents = self.docket.parents
799 799 else:
800 800 self._rustmap, parents = rustmod.DirstateMap.new_v1(
801 801 self._use_dirstate_tree, self._readdirstatefile()
802 802 )
803 803
804 804 if parents and not self._dirtyparents:
805 805 self.setparents(*parents)
806 806
807 807 self.__contains__ = self._rustmap.__contains__
808 808 self.__getitem__ = self._rustmap.__getitem__
809 809 self.get = self._rustmap.get
810 810 return self._rustmap
811 811
812 812 def write(self, tr, st, now):
813 813 if not self._use_dirstate_v2:
814 814 p1, p2 = self.parents()
815 815 packed = self._rustmap.write_v1(p1, p2, now)
816 816 st.write(packed)
817 817 st.close()
818 818 self._dirtyparents = False
819 819 return
820 820
821 821 # We can only append to an existing data file if there is one
822 822 can_append = self.docket.uuid is not None
823 823 packed, meta, append = self._rustmap.write_v2(now, can_append)
824 824 if append:
825 825 docket = self.docket
826 826 data_filename = docket.data_filename()
827 827 if tr:
828 828 tr.add(data_filename, docket.data_size)
829 829 with self._opener(data_filename, b'r+b') as fp:
830 830 fp.seek(docket.data_size)
831 831 assert fp.tell() == docket.data_size
832 832 written = fp.write(packed)
833 833 if written is not None: # py2 may return None
834 834 assert written == len(packed), (written, len(packed))
835 835 docket.data_size += len(packed)
836 836 docket.parents = self.parents()
837 837 docket.tree_metadata = meta
838 838 st.write(docket.serialize())
839 839 st.close()
840 840 else:
841 841 old_docket = self.docket
842 842 new_docket = docketmod.DirstateDocket.with_new_uuid(
843 843 self.parents(), len(packed), meta
844 844 )
845 845 data_filename = new_docket.data_filename()
846 846 if tr:
847 847 tr.add(data_filename, 0)
848 848 self._opener.write(data_filename, packed)
849 849 # Write the new docket after the new data file has been
850 850 # written. Because `st` was opened with `atomictemp=True`,
851 851 # the actual `.hg/dirstate` file is only affected on close.
852 852 st.write(new_docket.serialize())
853 853 st.close()
854 854 # Remove the old data file after the new docket pointing to
855 855 # the new data file was written.
856 856 if old_docket.uuid:
857 857 data_filename = old_docket.data_filename()
858 858 unlink = lambda _tr=None: self._opener.unlink(data_filename)
859 859 if tr:
860 860 category = b"dirstate-v2-clean-" + old_docket.uuid
861 861 tr.addpostclose(category, unlink)
862 862 else:
863 863 unlink()
864 864 self._docket = new_docket
865 865 # Reload from the newly-written file
866 866 util.clearcachedproperty(self, b"_rustmap")
867 867 self._dirtyparents = False
868 868
869 869 @propertycache
870 870 def filefoldmap(self):
871 871 """Returns a dictionary mapping normalized case paths to their
872 872 non-normalized versions.
873 873 """
874 874 return self._rustmap.filefoldmapasdict()
875 875
876 876 def hastrackeddir(self, d):
877 877 return self._rustmap.hastrackeddir(d)
878 878
879 879 def hasdir(self, d):
880 880 return self._rustmap.hasdir(d)
881 881
882 882 @propertycache
883 883 def identity(self):
884 884 self._rustmap
885 885 return self.identity
886 886
887 887 @property
888 888 def nonnormalset(self):
889 889 nonnorm = self._rustmap.non_normal_entries()
890 890 return nonnorm
891 891
892 892 @propertycache
893 893 def otherparentset(self):
894 894 otherparents = self._rustmap.other_parent_entries()
895 895 return otherparents
896 896
897 897 def non_normal_or_other_parent_paths(self):
898 898 return self._rustmap.non_normal_or_other_parent_paths()
899 899
900 900 @propertycache
901 901 def dirfoldmap(self):
902 902 f = {}
903 903 normcase = util.normcase
904 904 for name in self._rustmap.tracked_dirs():
905 905 f[normcase(name)] = name
906 906 return f
907 907
908 908 def set_possibly_dirty(self, filename):
909 909 """record that the current state of the file on disk is unknown"""
910 910 entry = self[filename]
911 911 entry.set_possibly_dirty()
912 912 self._rustmap.set_v1(filename, entry)
913 913
914 914 def __setitem__(self, key, value):
915 915 assert isinstance(value, DirstateItem)
916 916 self._rustmap.set_v1(key, value)
General Comments 0
You need to be logged in to leave comments. Login now