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