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