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