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