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