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