##// END OF EJS Templates
dirstatemap: use the default code to handle "added" case...
marmoute -
r48714:6816ae36 default
parent child Browse files
Show More
@@ -1,934 +1,933 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 return
260 260 elif merged:
261 261 # XXX might be merged and removed ?
262 262 entry = self.get(filename)
263 263 if entry is not None and entry.tracked:
264 264 # XXX mostly replicate dirstate.other parent. We should get
265 265 # the higher layer to pass us more reliable data where `merged`
266 266 # actually mean merged. Dropping the else clause will show
267 267 # failure in `test-graft.t`
268 268 self.addfile(filename, merged=True)
269 269 return
270 270 else:
271 271 self.addfile(filename, from_p2=True)
272 272 return
273 273 elif not (p1_tracked or p2_tracked) and wc_tracked:
274 self.addfile(filename, added=True, possibly_dirty=possibly_dirty)
275 return
274 pass # file is added, nothing special to adjust
276 275 elif (p1_tracked or p2_tracked) and not wc_tracked:
277 276 pass
278 277 elif clean_p2 and wc_tracked:
279 278 if p1_tracked or self.get(filename) is not None:
280 279 # XXX the `self.get` call is catching some case in
281 280 # `test-merge-remove.t` where the file is tracked in p1, the
282 281 # p1_tracked argument is False.
283 282 #
284 283 # In addition, this seems to be a case where the file is marked
285 284 # as merged without actually being the result of a merge
286 285 # action. So thing are not ideal here.
287 286 merged = True
288 287 clean_p2 = False
289 288 elif not p1_tracked and p2_tracked and wc_tracked:
290 289 clean_p2 = True
291 290 elif possibly_dirty:
292 291 pass
293 292 elif wc_tracked:
294 293 # this is a "normal" file
295 294 if parentfiledata is None:
296 295 msg = b'failed to pass parentfiledata for a normal file: %s'
297 296 msg %= filename
298 297 raise error.ProgrammingError(msg)
299 298 else:
300 299 assert False, 'unreachable'
301 300
302 301 old_entry = self._map.get(filename)
303 302 self._dirs_incr(filename, old_entry)
304 303 entry = DirstateItem(
305 304 wc_tracked=wc_tracked,
306 305 p1_tracked=p1_tracked,
307 306 p2_tracked=p2_tracked,
308 307 merged=merged,
309 308 clean_p1=clean_p1,
310 309 clean_p2=clean_p2,
311 310 possibly_dirty=possibly_dirty,
312 311 parentfiledata=parentfiledata,
313 312 )
314 313 if entry.dm_nonnormal:
315 314 self.nonnormalset.add(filename)
316 315 else:
317 316 self.nonnormalset.discard(filename)
318 317 if entry.dm_otherparent:
319 318 self.otherparentset.add(filename)
320 319 else:
321 320 self.otherparentset.discard(filename)
322 321 self._map[filename] = entry
323 322
324 323 def set_untracked(self, f):
325 324 """Mark a file as no longer tracked in the dirstate map"""
326 325 entry = self[f]
327 326 self._dirs_decr(f, old_entry=entry, remove_variant=True)
328 327 if entry.from_p2:
329 328 self.otherparentset.add(f)
330 329 elif not entry.merged:
331 330 self.copymap.pop(f, None)
332 331 entry.set_untracked()
333 332 self.nonnormalset.add(f)
334 333
335 334 def dropfile(self, f):
336 335 """
337 336 Remove a file from the dirstate. Returns True if the file was
338 337 previously recorded.
339 338 """
340 339 old_entry = self._map.pop(f, None)
341 340 self._dirs_decr(f, old_entry=old_entry)
342 341 self.nonnormalset.discard(f)
343 342 return old_entry is not None
344 343
345 344 def clearambiguoustimes(self, files, now):
346 345 for f in files:
347 346 e = self.get(f)
348 347 if e is not None and e.need_delay(now):
349 348 e.set_possibly_dirty()
350 349 self.nonnormalset.add(f)
351 350
352 351 def nonnormalentries(self):
353 352 '''Compute the nonnormal dirstate entries from the dmap'''
354 353 try:
355 354 return parsers.nonnormalotherparententries(self._map)
356 355 except AttributeError:
357 356 nonnorm = set()
358 357 otherparent = set()
359 358 for fname, e in pycompat.iteritems(self._map):
360 359 if e.dm_nonnormal:
361 360 nonnorm.add(fname)
362 361 if e.from_p2:
363 362 otherparent.add(fname)
364 363 return nonnorm, otherparent
365 364
366 365 @propertycache
367 366 def filefoldmap(self):
368 367 """Returns a dictionary mapping normalized case paths to their
369 368 non-normalized versions.
370 369 """
371 370 try:
372 371 makefilefoldmap = parsers.make_file_foldmap
373 372 except AttributeError:
374 373 pass
375 374 else:
376 375 return makefilefoldmap(
377 376 self._map, util.normcasespec, util.normcasefallback
378 377 )
379 378
380 379 f = {}
381 380 normcase = util.normcase
382 381 for name, s in pycompat.iteritems(self._map):
383 382 if not s.removed:
384 383 f[normcase(name)] = name
385 384 f[b'.'] = b'.' # prevents useless util.fspath() invocation
386 385 return f
387 386
388 387 def hastrackeddir(self, d):
389 388 """
390 389 Returns True if the dirstate contains a tracked (not removed) file
391 390 in this directory.
392 391 """
393 392 return d in self._dirs
394 393
395 394 def hasdir(self, d):
396 395 """
397 396 Returns True if the dirstate contains a file (tracked or removed)
398 397 in this directory.
399 398 """
400 399 return d in self._alldirs
401 400
402 401 @propertycache
403 402 def _dirs(self):
404 403 return pathutil.dirs(self._map, b'r')
405 404
406 405 @propertycache
407 406 def _alldirs(self):
408 407 return pathutil.dirs(self._map)
409 408
410 409 def _opendirstatefile(self):
411 410 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
412 411 if self._pendingmode is not None and self._pendingmode != mode:
413 412 fp.close()
414 413 raise error.Abort(
415 414 _(b'working directory state may be changed parallelly')
416 415 )
417 416 self._pendingmode = mode
418 417 return fp
419 418
420 419 def parents(self):
421 420 if not self._parents:
422 421 try:
423 422 fp = self._opendirstatefile()
424 423 st = fp.read(2 * self._nodelen)
425 424 fp.close()
426 425 except IOError as err:
427 426 if err.errno != errno.ENOENT:
428 427 raise
429 428 # File doesn't exist, so the current state is empty
430 429 st = b''
431 430
432 431 l = len(st)
433 432 if l == self._nodelen * 2:
434 433 self._parents = (
435 434 st[: self._nodelen],
436 435 st[self._nodelen : 2 * self._nodelen],
437 436 )
438 437 elif l == 0:
439 438 self._parents = (
440 439 self._nodeconstants.nullid,
441 440 self._nodeconstants.nullid,
442 441 )
443 442 else:
444 443 raise error.Abort(
445 444 _(b'working directory state appears damaged!')
446 445 )
447 446
448 447 return self._parents
449 448
450 449 def setparents(self, p1, p2):
451 450 self._parents = (p1, p2)
452 451 self._dirtyparents = True
453 452
454 453 def read(self):
455 454 # ignore HG_PENDING because identity is used only for writing
456 455 self.identity = util.filestat.frompath(
457 456 self._opener.join(self._filename)
458 457 )
459 458
460 459 try:
461 460 fp = self._opendirstatefile()
462 461 try:
463 462 st = fp.read()
464 463 finally:
465 464 fp.close()
466 465 except IOError as err:
467 466 if err.errno != errno.ENOENT:
468 467 raise
469 468 return
470 469 if not st:
471 470 return
472 471
473 472 if util.safehasattr(parsers, b'dict_new_presized'):
474 473 # Make an estimate of the number of files in the dirstate based on
475 474 # its size. This trades wasting some memory for avoiding costly
476 475 # resizes. Each entry have a prefix of 17 bytes followed by one or
477 476 # two path names. Studies on various large-scale real-world repositories
478 477 # found 54 bytes a reasonable upper limit for the average path names.
479 478 # Copy entries are ignored for the sake of this estimate.
480 479 self._map = parsers.dict_new_presized(len(st) // 71)
481 480
482 481 # Python's garbage collector triggers a GC each time a certain number
483 482 # of container objects (the number being defined by
484 483 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
485 484 # for each file in the dirstate. The C version then immediately marks
486 485 # them as not to be tracked by the collector. However, this has no
487 486 # effect on when GCs are triggered, only on what objects the GC looks
488 487 # into. This means that O(number of files) GCs are unavoidable.
489 488 # Depending on when in the process's lifetime the dirstate is parsed,
490 489 # this can get very expensive. As a workaround, disable GC while
491 490 # parsing the dirstate.
492 491 #
493 492 # (we cannot decorate the function directly since it is in a C module)
494 493 parse_dirstate = util.nogc(parsers.parse_dirstate)
495 494 p = parse_dirstate(self._map, self.copymap, st)
496 495 if not self._dirtyparents:
497 496 self.setparents(*p)
498 497
499 498 # Avoid excess attribute lookups by fast pathing certain checks
500 499 self.__contains__ = self._map.__contains__
501 500 self.__getitem__ = self._map.__getitem__
502 501 self.get = self._map.get
503 502
504 503 def write(self, _tr, st, now):
505 504 st.write(
506 505 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
507 506 )
508 507 st.close()
509 508 self._dirtyparents = False
510 509 self.nonnormalset, self.otherparentset = self.nonnormalentries()
511 510
512 511 @propertycache
513 512 def nonnormalset(self):
514 513 nonnorm, otherparents = self.nonnormalentries()
515 514 self.otherparentset = otherparents
516 515 return nonnorm
517 516
518 517 @propertycache
519 518 def otherparentset(self):
520 519 nonnorm, otherparents = self.nonnormalentries()
521 520 self.nonnormalset = nonnorm
522 521 return otherparents
523 522
524 523 def non_normal_or_other_parent_paths(self):
525 524 return self.nonnormalset.union(self.otherparentset)
526 525
527 526 @propertycache
528 527 def identity(self):
529 528 self._map
530 529 return self.identity
531 530
532 531 @propertycache
533 532 def dirfoldmap(self):
534 533 f = {}
535 534 normcase = util.normcase
536 535 for name in self._dirs:
537 536 f[normcase(name)] = name
538 537 return f
539 538
540 539
541 540 if rustmod is not None:
542 541
543 542 class dirstatemap(object):
544 543 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
545 544 self._use_dirstate_v2 = use_dirstate_v2
546 545 self._nodeconstants = nodeconstants
547 546 self._ui = ui
548 547 self._opener = opener
549 548 self._root = root
550 549 self._filename = b'dirstate'
551 550 self._nodelen = 20 # Also update Rust code when changing this!
552 551 self._parents = None
553 552 self._dirtyparents = False
554 553 self._docket = None
555 554
556 555 # for consistent view between _pl() and _read() invocations
557 556 self._pendingmode = None
558 557
559 558 self._use_dirstate_tree = self._ui.configbool(
560 559 b"experimental",
561 560 b"dirstate-tree.in-memory",
562 561 False,
563 562 )
564 563
565 564 def addfile(
566 565 self,
567 566 f,
568 567 mode=0,
569 568 size=None,
570 569 mtime=None,
571 570 added=False,
572 571 merged=False,
573 572 from_p2=False,
574 573 possibly_dirty=False,
575 574 ):
576 575 return self._rustmap.addfile(
577 576 f,
578 577 mode,
579 578 size,
580 579 mtime,
581 580 added,
582 581 merged,
583 582 from_p2,
584 583 possibly_dirty,
585 584 )
586 585
587 586 def reset_state(
588 587 self,
589 588 filename,
590 589 wc_tracked,
591 590 p1_tracked,
592 591 p2_tracked=False,
593 592 merged=False,
594 593 clean_p1=False,
595 594 clean_p2=False,
596 595 possibly_dirty=False,
597 596 parentfiledata=None,
598 597 ):
599 598 """Set a entry to a given state, disregarding all previous state
600 599
601 600 This is to be used by the part of the dirstate API dedicated to
602 601 adjusting the dirstate after a update/merge.
603 602
604 603 note: calling this might result to no entry existing at all if the
605 604 dirstate map does not see any point at having one for this file
606 605 anymore.
607 606 """
608 607 if merged and (clean_p1 or clean_p2):
609 608 msg = (
610 609 b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
611 610 )
612 611 raise error.ProgrammingError(msg)
613 612 # copy information are now outdated
614 613 # (maybe new information should be in directly passed to this function)
615 614 self.copymap.pop(filename, None)
616 615
617 616 if not (p1_tracked or p2_tracked or wc_tracked):
618 617 self.dropfile(filename)
619 618 elif merged:
620 619 # XXX might be merged and removed ?
621 620 entry = self.get(filename)
622 621 if entry is not None and entry.tracked:
623 622 # XXX mostly replicate dirstate.other parent. We should get
624 623 # the higher layer to pass us more reliable data where `merged`
625 624 # actually mean merged. Dropping the else clause will show
626 625 # failure in `test-graft.t`
627 626 self.addfile(filename, merged=True)
628 627 else:
629 628 self.addfile(filename, from_p2=True)
630 629 elif not (p1_tracked or p2_tracked) and wc_tracked:
631 630 self.addfile(
632 631 filename, added=True, possibly_dirty=possibly_dirty
633 632 )
634 633 elif (p1_tracked or p2_tracked) and not wc_tracked:
635 634 # XXX might be merged and removed ?
636 635 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
637 636 self.nonnormalset.add(filename)
638 637 elif clean_p2 and wc_tracked:
639 638 if p1_tracked or self.get(filename) is not None:
640 639 # XXX the `self.get` call is catching some case in
641 640 # `test-merge-remove.t` where the file is tracked in p1, the
642 641 # p1_tracked argument is False.
643 642 #
644 643 # In addition, this seems to be a case where the file is marked
645 644 # as merged without actually being the result of a merge
646 645 # action. So thing are not ideal here.
647 646 self.addfile(filename, merged=True)
648 647 else:
649 648 self.addfile(filename, from_p2=True)
650 649 elif not p1_tracked and p2_tracked and wc_tracked:
651 650 self.addfile(
652 651 filename, from_p2=True, possibly_dirty=possibly_dirty
653 652 )
654 653 elif possibly_dirty:
655 654 self.addfile(filename, possibly_dirty=possibly_dirty)
656 655 elif wc_tracked:
657 656 # this is a "normal" file
658 657 if parentfiledata is None:
659 658 msg = b'failed to pass parentfiledata for a normal file: %s'
660 659 msg %= filename
661 660 raise error.ProgrammingError(msg)
662 661 mode, size, mtime = parentfiledata
663 662 self.addfile(filename, mode=mode, size=size, mtime=mtime)
664 663 self.nonnormalset.discard(filename)
665 664 else:
666 665 assert False, 'unreachable'
667 666
668 667 def set_untracked(self, f):
669 668 """Mark a file as no longer tracked in the dirstate map"""
670 669 # in merge is only trigger more logic, so it "fine" to pass it.
671 670 #
672 671 # the inner rust dirstate map code need to be adjusted once the API
673 672 # for dirstate/dirstatemap/DirstateItem is a bit more settled
674 673 self._rustmap.removefile(f, in_merge=True)
675 674
676 675 def removefile(self, *args, **kwargs):
677 676 return self._rustmap.removefile(*args, **kwargs)
678 677
679 678 def dropfile(self, *args, **kwargs):
680 679 return self._rustmap.dropfile(*args, **kwargs)
681 680
682 681 def clearambiguoustimes(self, *args, **kwargs):
683 682 return self._rustmap.clearambiguoustimes(*args, **kwargs)
684 683
685 684 def nonnormalentries(self):
686 685 return self._rustmap.nonnormalentries()
687 686
688 687 def get(self, *args, **kwargs):
689 688 return self._rustmap.get(*args, **kwargs)
690 689
691 690 @property
692 691 def copymap(self):
693 692 return self._rustmap.copymap()
694 693
695 694 def directories(self):
696 695 return self._rustmap.directories()
697 696
698 697 def debug_iter(self):
699 698 return self._rustmap.debug_iter()
700 699
701 700 def preload(self):
702 701 self._rustmap
703 702
704 703 def clear(self):
705 704 self._rustmap.clear()
706 705 self.setparents(
707 706 self._nodeconstants.nullid, self._nodeconstants.nullid
708 707 )
709 708 util.clearcachedproperty(self, b"_dirs")
710 709 util.clearcachedproperty(self, b"_alldirs")
711 710 util.clearcachedproperty(self, b"dirfoldmap")
712 711
713 712 def items(self):
714 713 return self._rustmap.items()
715 714
716 715 def keys(self):
717 716 return iter(self._rustmap)
718 717
719 718 def __contains__(self, key):
720 719 return key in self._rustmap
721 720
722 721 def __getitem__(self, item):
723 722 return self._rustmap[item]
724 723
725 724 def __len__(self):
726 725 return len(self._rustmap)
727 726
728 727 def __iter__(self):
729 728 return iter(self._rustmap)
730 729
731 730 # forward for python2,3 compat
732 731 iteritems = items
733 732
734 733 def _opendirstatefile(self):
735 734 fp, mode = txnutil.trypending(
736 735 self._root, self._opener, self._filename
737 736 )
738 737 if self._pendingmode is not None and self._pendingmode != mode:
739 738 fp.close()
740 739 raise error.Abort(
741 740 _(b'working directory state may be changed parallelly')
742 741 )
743 742 self._pendingmode = mode
744 743 return fp
745 744
746 745 def _readdirstatefile(self, size=-1):
747 746 try:
748 747 with self._opendirstatefile() as fp:
749 748 return fp.read(size)
750 749 except IOError as err:
751 750 if err.errno != errno.ENOENT:
752 751 raise
753 752 # File doesn't exist, so the current state is empty
754 753 return b''
755 754
756 755 def setparents(self, p1, p2):
757 756 self._parents = (p1, p2)
758 757 self._dirtyparents = True
759 758
760 759 def parents(self):
761 760 if not self._parents:
762 761 if self._use_dirstate_v2:
763 762 self._parents = self.docket.parents
764 763 else:
765 764 read_len = self._nodelen * 2
766 765 st = self._readdirstatefile(read_len)
767 766 l = len(st)
768 767 if l == read_len:
769 768 self._parents = (
770 769 st[: self._nodelen],
771 770 st[self._nodelen : 2 * self._nodelen],
772 771 )
773 772 elif l == 0:
774 773 self._parents = (
775 774 self._nodeconstants.nullid,
776 775 self._nodeconstants.nullid,
777 776 )
778 777 else:
779 778 raise error.Abort(
780 779 _(b'working directory state appears damaged!')
781 780 )
782 781
783 782 return self._parents
784 783
785 784 @property
786 785 def docket(self):
787 786 if not self._docket:
788 787 if not self._use_dirstate_v2:
789 788 raise error.ProgrammingError(
790 789 b'dirstate only has a docket in v2 format'
791 790 )
792 791 self._docket = docketmod.DirstateDocket.parse(
793 792 self._readdirstatefile(), self._nodeconstants
794 793 )
795 794 return self._docket
796 795
797 796 @propertycache
798 797 def _rustmap(self):
799 798 """
800 799 Fills the Dirstatemap when called.
801 800 """
802 801 # ignore HG_PENDING because identity is used only for writing
803 802 self.identity = util.filestat.frompath(
804 803 self._opener.join(self._filename)
805 804 )
806 805
807 806 if self._use_dirstate_v2:
808 807 if self.docket.uuid:
809 808 # TODO: use mmap when possible
810 809 data = self._opener.read(self.docket.data_filename())
811 810 else:
812 811 data = b''
813 812 self._rustmap = rustmod.DirstateMap.new_v2(
814 813 data, self.docket.data_size, self.docket.tree_metadata
815 814 )
816 815 parents = self.docket.parents
817 816 else:
818 817 self._rustmap, parents = rustmod.DirstateMap.new_v1(
819 818 self._use_dirstate_tree, self._readdirstatefile()
820 819 )
821 820
822 821 if parents and not self._dirtyparents:
823 822 self.setparents(*parents)
824 823
825 824 self.__contains__ = self._rustmap.__contains__
826 825 self.__getitem__ = self._rustmap.__getitem__
827 826 self.get = self._rustmap.get
828 827 return self._rustmap
829 828
830 829 def write(self, tr, st, now):
831 830 if not self._use_dirstate_v2:
832 831 p1, p2 = self.parents()
833 832 packed = self._rustmap.write_v1(p1, p2, now)
834 833 st.write(packed)
835 834 st.close()
836 835 self._dirtyparents = False
837 836 return
838 837
839 838 # We can only append to an existing data file if there is one
840 839 can_append = self.docket.uuid is not None
841 840 packed, meta, append = self._rustmap.write_v2(now, can_append)
842 841 if append:
843 842 docket = self.docket
844 843 data_filename = docket.data_filename()
845 844 if tr:
846 845 tr.add(data_filename, docket.data_size)
847 846 with self._opener(data_filename, b'r+b') as fp:
848 847 fp.seek(docket.data_size)
849 848 assert fp.tell() == docket.data_size
850 849 written = fp.write(packed)
851 850 if written is not None: # py2 may return None
852 851 assert written == len(packed), (written, len(packed))
853 852 docket.data_size += len(packed)
854 853 docket.parents = self.parents()
855 854 docket.tree_metadata = meta
856 855 st.write(docket.serialize())
857 856 st.close()
858 857 else:
859 858 old_docket = self.docket
860 859 new_docket = docketmod.DirstateDocket.with_new_uuid(
861 860 self.parents(), len(packed), meta
862 861 )
863 862 data_filename = new_docket.data_filename()
864 863 if tr:
865 864 tr.add(data_filename, 0)
866 865 self._opener.write(data_filename, packed)
867 866 # Write the new docket after the new data file has been
868 867 # written. Because `st` was opened with `atomictemp=True`,
869 868 # the actual `.hg/dirstate` file is only affected on close.
870 869 st.write(new_docket.serialize())
871 870 st.close()
872 871 # Remove the old data file after the new docket pointing to
873 872 # the new data file was written.
874 873 if old_docket.uuid:
875 874 data_filename = old_docket.data_filename()
876 875 unlink = lambda _tr=None: self._opener.unlink(data_filename)
877 876 if tr:
878 877 category = b"dirstate-v2-clean-" + old_docket.uuid
879 878 tr.addpostclose(category, unlink)
880 879 else:
881 880 unlink()
882 881 self._docket = new_docket
883 882 # Reload from the newly-written file
884 883 util.clearcachedproperty(self, b"_rustmap")
885 884 self._dirtyparents = False
886 885
887 886 @propertycache
888 887 def filefoldmap(self):
889 888 """Returns a dictionary mapping normalized case paths to their
890 889 non-normalized versions.
891 890 """
892 891 return self._rustmap.filefoldmapasdict()
893 892
894 893 def hastrackeddir(self, d):
895 894 return self._rustmap.hastrackeddir(d)
896 895
897 896 def hasdir(self, d):
898 897 return self._rustmap.hasdir(d)
899 898
900 899 @propertycache
901 900 def identity(self):
902 901 self._rustmap
903 902 return self.identity
904 903
905 904 @property
906 905 def nonnormalset(self):
907 906 nonnorm = self._rustmap.non_normal_entries()
908 907 return nonnorm
909 908
910 909 @propertycache
911 910 def otherparentset(self):
912 911 otherparents = self._rustmap.other_parent_entries()
913 912 return otherparents
914 913
915 914 def non_normal_or_other_parent_paths(self):
916 915 return self._rustmap.non_normal_or_other_parent_paths()
917 916
918 917 @propertycache
919 918 def dirfoldmap(self):
920 919 f = {}
921 920 normcase = util.normcase
922 921 for name in self._rustmap.tracked_dirs():
923 922 f[normcase(name)] = name
924 923 return f
925 924
926 925 def set_possibly_dirty(self, filename):
927 926 """record that the current state of the file on disk is unknown"""
928 927 entry = self[filename]
929 928 entry.set_possibly_dirty()
930 929 self._rustmap.set_v1(filename, entry)
931 930
932 931 def __setitem__(self, key, value):
933 932 assert isinstance(value, DirstateItem)
934 933 self._rustmap.set_v1(key, value)
General Comments 0
You need to be logged in to leave comments. Login now