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