##// END OF EJS Templates
dirstate: factor the identity setting code in the dirstate map...
marmoute -
r51136:342c3c46 stable
parent child Browse files
Show More
@@ -1,732 +1,733
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
7 7 from .i18n import _
8 8
9 9 from . import (
10 10 error,
11 11 pathutil,
12 12 policy,
13 13 testing,
14 14 txnutil,
15 15 util,
16 16 )
17 17
18 18 from .dirstateutils import (
19 19 docket as docketmod,
20 20 v2,
21 21 )
22 22
23 23 parsers = policy.importmod('parsers')
24 24 rustmod = policy.importrust('dirstate')
25 25
26 26 propertycache = util.propertycache
27 27
28 28 if rustmod is None:
29 29 DirstateItem = parsers.DirstateItem
30 30 else:
31 31 DirstateItem = rustmod.DirstateItem
32 32
33 33 rangemask = 0x7FFFFFFF
34 34
35 35 WRITE_MODE_AUTO = 0
36 36 WRITE_MODE_FORCE_NEW = 1
37 37 WRITE_MODE_FORCE_APPEND = 2
38 38
39 39
40 40 V2_MAX_READ_ATTEMPTS = 5
41 41
42 42
43 43 class _dirstatemapcommon:
44 44 """
45 45 Methods that are identical for both implementations of the dirstatemap
46 46 class, with and without Rust extensions enabled.
47 47 """
48 48
49 49 # please pytype
50 50
51 51 _map = None
52 52 copymap = None
53 53
54 54 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
55 55 self._use_dirstate_v2 = use_dirstate_v2
56 56 self._nodeconstants = nodeconstants
57 57 self._ui = ui
58 58 self._opener = opener
59 59 self._root = root
60 60 self._filename = b'dirstate'
61 61 self._nodelen = 20 # Also update Rust code when changing this!
62 62 self._parents = None
63 63 self._dirtyparents = False
64 64 self._docket = None
65 65 write_mode = ui.config(b"devel", b"dirstate.v2.data_update_mode")
66 66 if write_mode == b"auto":
67 67 self._write_mode = WRITE_MODE_AUTO
68 68 elif write_mode == b"force-append":
69 69 self._write_mode = WRITE_MODE_FORCE_APPEND
70 70 elif write_mode == b"force-new":
71 71 self._write_mode = WRITE_MODE_FORCE_NEW
72 72 else:
73 73 # unknown value, fallback to default
74 74 self._write_mode = WRITE_MODE_AUTO
75 75
76 76 # for consistent view between _pl() and _read() invocations
77 77 self._pendingmode = None
78 78
79 def _set_identity(self):
80 # ignore HG_PENDING because identity is used only for writing
81 file_path = self._opener.join(self._filename)
82 self.identity = util.filestat.frompath(file_path)
83
79 84 def preload(self):
80 85 """Loads the underlying data, if it's not already loaded"""
81 86 self._map
82 87
83 88 def get(self, key, default=None):
84 89 return self._map.get(key, default)
85 90
86 91 def __len__(self):
87 92 return len(self._map)
88 93
89 94 def __iter__(self):
90 95 return iter(self._map)
91 96
92 97 def __contains__(self, key):
93 98 return key in self._map
94 99
95 100 def __getitem__(self, item):
96 101 return self._map[item]
97 102
98 103 ### disk interaction
99 104
100 105 def _opendirstatefile(self):
101 106 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
102 107 if self._pendingmode is not None and self._pendingmode != mode:
103 108 fp.close()
104 109 raise error.Abort(
105 110 _(b'working directory state may be changed parallelly')
106 111 )
107 112 self._pendingmode = mode
108 113 return fp
109 114
110 115 def _readdirstatefile(self, size=-1):
111 116 try:
112 117 with self._opendirstatefile() as fp:
113 118 return fp.read(size)
114 119 except FileNotFoundError:
115 120 # File doesn't exist, so the current state is empty
116 121 return b''
117 122
118 123 @property
119 124 def docket(self):
120 125 if not self._docket:
121 126 if not self._use_dirstate_v2:
122 127 raise error.ProgrammingError(
123 128 b'dirstate only has a docket in v2 format'
124 129 )
125 130 self._docket = docketmod.DirstateDocket.parse(
126 131 self._readdirstatefile(), self._nodeconstants
127 132 )
128 133 return self._docket
129 134
130 135 def _read_v2_data(self):
131 136 data = None
132 137 attempts = 0
133 138 while attempts < V2_MAX_READ_ATTEMPTS:
134 139 attempts += 1
135 140 try:
136 141 # TODO: use mmap when possible
137 142 data = self._opener.read(self.docket.data_filename())
138 143 except FileNotFoundError:
139 144 # read race detected between docket and data file
140 145 # reload the docket and retry
141 146 self._docket = None
142 147 if data is None:
143 148 assert attempts >= V2_MAX_READ_ATTEMPTS
144 149 msg = b"dirstate read race happened %d times in a row"
145 150 msg %= attempts
146 151 raise error.Abort(msg)
147 152 return self._opener.read(self.docket.data_filename())
148 153
149 154 def write_v2_no_append(self, tr, st, meta, packed):
150 155 old_docket = self.docket
151 156 new_docket = docketmod.DirstateDocket.with_new_uuid(
152 157 self.parents(), len(packed), meta
153 158 )
154 159 if old_docket.uuid == new_docket.uuid:
155 160 raise error.ProgrammingError(b'dirstate docket name collision')
156 161 data_filename = new_docket.data_filename()
157 162 self._opener.write(data_filename, packed)
158 163 # Write the new docket after the new data file has been
159 164 # written. Because `st` was opened with `atomictemp=True`,
160 165 # the actual `.hg/dirstate` file is only affected on close.
161 166 st.write(new_docket.serialize())
162 167 st.close()
163 168 # Remove the old data file after the new docket pointing to
164 169 # the new data file was written.
165 170 if old_docket.uuid:
166 171 data_filename = old_docket.data_filename()
167 172 unlink = lambda _tr=None: self._opener.unlink(data_filename)
168 173 if tr:
169 174 category = b"dirstate-v2-clean-" + old_docket.uuid
170 175 tr.addpostclose(category, unlink)
171 176 else:
172 177 unlink()
173 178 self._docket = new_docket
174 179
175 180 ### reading/setting parents
176 181
177 182 def parents(self):
178 183 if not self._parents:
179 184 if self._use_dirstate_v2:
180 185 self._parents = self.docket.parents
181 186 else:
182 187 read_len = self._nodelen * 2
183 188 st = self._readdirstatefile(read_len)
184 189 l = len(st)
185 190 if l == read_len:
186 191 self._parents = (
187 192 st[: self._nodelen],
188 193 st[self._nodelen : 2 * self._nodelen],
189 194 )
190 195 elif l == 0:
191 196 self._parents = (
192 197 self._nodeconstants.nullid,
193 198 self._nodeconstants.nullid,
194 199 )
195 200 else:
196 201 raise error.Abort(
197 202 _(b'working directory state appears damaged!')
198 203 )
199 204
200 205 return self._parents
201 206
202 207
203 208 class dirstatemap(_dirstatemapcommon):
204 209 """Map encapsulating the dirstate's contents.
205 210
206 211 The dirstate contains the following state:
207 212
208 213 - `identity` is the identity of the dirstate file, which can be used to
209 214 detect when changes have occurred to the dirstate file.
210 215
211 216 - `parents` is a pair containing the parents of the working copy. The
212 217 parents are updated by calling `setparents`.
213 218
214 219 - the state map maps filenames to tuples of (state, mode, size, mtime),
215 220 where state is a single character representing 'normal', 'added',
216 221 'removed', or 'merged'. It is read by treating the dirstate as a
217 222 dict. File state is updated by calling various methods (see each
218 223 documentation for details):
219 224
220 225 - `reset_state`,
221 226 - `set_tracked`
222 227 - `set_untracked`
223 228 - `set_clean`
224 229 - `set_possibly_dirty`
225 230
226 231 - `copymap` maps destination filenames to their source filename.
227 232
228 233 The dirstate also provides the following views onto the state:
229 234
230 235 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
231 236 form that they appear as in the dirstate.
232 237
233 238 - `dirfoldmap` is a dict mapping normalized directory names to the
234 239 denormalized form that they appear as in the dirstate.
235 240 """
236 241
237 242 ### Core data storage and access
238 243
239 244 @propertycache
240 245 def _map(self):
241 246 self._map = {}
242 247 self.read()
243 248 return self._map
244 249
245 250 @propertycache
246 251 def copymap(self):
247 252 self.copymap = {}
248 253 self._map
249 254 return self.copymap
250 255
251 256 def clear(self):
252 257 self._map.clear()
253 258 self.copymap.clear()
254 259 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
255 260 util.clearcachedproperty(self, b"_dirs")
256 261 util.clearcachedproperty(self, b"_alldirs")
257 262 util.clearcachedproperty(self, b"filefoldmap")
258 263 util.clearcachedproperty(self, b"dirfoldmap")
259 264
260 265 def items(self):
261 266 return self._map.items()
262 267
263 268 # forward for python2,3 compat
264 269 iteritems = items
265 270
266 271 def debug_iter(self, all):
267 272 """
268 273 Return an iterator of (filename, state, mode, size, mtime) tuples
269 274
270 275 `all` is unused when Rust is not enabled
271 276 """
272 277 for (filename, item) in self.items():
273 278 yield (filename, item.state, item.mode, item.size, item.mtime)
274 279
275 280 def keys(self):
276 281 return self._map.keys()
277 282
278 283 ### reading/setting parents
279 284
280 285 def setparents(self, p1, p2, fold_p2=False):
281 286 self._parents = (p1, p2)
282 287 self._dirtyparents = True
283 288 copies = {}
284 289 if fold_p2:
285 290 for f, s in self._map.items():
286 291 # Discard "merged" markers when moving away from a merge state
287 292 if s.p2_info:
288 293 source = self.copymap.pop(f, None)
289 294 if source:
290 295 copies[f] = source
291 296 s.drop_merge_data()
292 297 return copies
293 298
294 299 ### disk interaction
295 300
296 301 def read(self):
297 302 # ignore HG_PENDING because identity is used only for writing
298 self.identity = util.filestat.frompath(
299 self._opener.join(self._filename)
300 )
303 self._set_identity()
301 304
302 305 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
303 306 if self._use_dirstate_v2:
304 307
305 308 if not self.docket.uuid:
306 309 return
307 310 testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
308 311 st = self._read_v2_data()
309 312 else:
310 313 st = self._readdirstatefile()
311 314
312 315 if not st:
313 316 return
314 317
315 318 # TODO: adjust this estimate for dirstate-v2
316 319 if util.safehasattr(parsers, b'dict_new_presized'):
317 320 # Make an estimate of the number of files in the dirstate based on
318 321 # its size. This trades wasting some memory for avoiding costly
319 322 # resizes. Each entry have a prefix of 17 bytes followed by one or
320 323 # two path names. Studies on various large-scale real-world repositories
321 324 # found 54 bytes a reasonable upper limit for the average path names.
322 325 # Copy entries are ignored for the sake of this estimate.
323 326 self._map = parsers.dict_new_presized(len(st) // 71)
324 327
325 328 # Python's garbage collector triggers a GC each time a certain number
326 329 # of container objects (the number being defined by
327 330 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
328 331 # for each file in the dirstate. The C version then immediately marks
329 332 # them as not to be tracked by the collector. However, this has no
330 333 # effect on when GCs are triggered, only on what objects the GC looks
331 334 # into. This means that O(number of files) GCs are unavoidable.
332 335 # Depending on when in the process's lifetime the dirstate is parsed,
333 336 # this can get very expensive. As a workaround, disable GC while
334 337 # parsing the dirstate.
335 338 #
336 339 # (we cannot decorate the function directly since it is in a C module)
337 340 if self._use_dirstate_v2:
338 341 p = self.docket.parents
339 342 meta = self.docket.tree_metadata
340 343 parse_dirstate = util.nogc(v2.parse_dirstate)
341 344 parse_dirstate(self._map, self.copymap, st, meta)
342 345 else:
343 346 parse_dirstate = util.nogc(parsers.parse_dirstate)
344 347 p = parse_dirstate(self._map, self.copymap, st)
345 348 if not self._dirtyparents:
346 349 self.setparents(*p)
347 350
348 351 # Avoid excess attribute lookups by fast pathing certain checks
349 352 self.__contains__ = self._map.__contains__
350 353 self.__getitem__ = self._map.__getitem__
351 354 self.get = self._map.get
352 355
353 356 def write(self, tr, st):
354 357 if self._use_dirstate_v2:
355 358 packed, meta = v2.pack_dirstate(self._map, self.copymap)
356 359 self.write_v2_no_append(tr, st, meta, packed)
357 360 else:
358 361 packed = parsers.pack_dirstate(
359 362 self._map, self.copymap, self.parents()
360 363 )
361 364 st.write(packed)
362 365 st.close()
363 366 self._dirtyparents = False
364 367
365 368 @propertycache
366 369 def identity(self):
367 370 self._map
368 371 return self.identity
369 372
370 373 ### code related to maintaining and accessing "extra" property
371 374 # (e.g. "has_dir")
372 375
373 376 def _dirs_incr(self, filename, old_entry=None):
374 377 """increment the dirstate counter if applicable"""
375 378 if (
376 379 old_entry is None or old_entry.removed
377 380 ) and "_dirs" in self.__dict__:
378 381 self._dirs.addpath(filename)
379 382 if old_entry is None and "_alldirs" in self.__dict__:
380 383 self._alldirs.addpath(filename)
381 384
382 385 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
383 386 """decrement the dirstate counter if applicable"""
384 387 if old_entry is not None:
385 388 if "_dirs" in self.__dict__ and not old_entry.removed:
386 389 self._dirs.delpath(filename)
387 390 if "_alldirs" in self.__dict__ and not remove_variant:
388 391 self._alldirs.delpath(filename)
389 392 elif remove_variant and "_alldirs" in self.__dict__:
390 393 self._alldirs.addpath(filename)
391 394 if "filefoldmap" in self.__dict__:
392 395 normed = util.normcase(filename)
393 396 self.filefoldmap.pop(normed, None)
394 397
395 398 @propertycache
396 399 def filefoldmap(self):
397 400 """Returns a dictionary mapping normalized case paths to their
398 401 non-normalized versions.
399 402 """
400 403 try:
401 404 makefilefoldmap = parsers.make_file_foldmap
402 405 except AttributeError:
403 406 pass
404 407 else:
405 408 return makefilefoldmap(
406 409 self._map, util.normcasespec, util.normcasefallback
407 410 )
408 411
409 412 f = {}
410 413 normcase = util.normcase
411 414 for name, s in self._map.items():
412 415 if not s.removed:
413 416 f[normcase(name)] = name
414 417 f[b'.'] = b'.' # prevents useless util.fspath() invocation
415 418 return f
416 419
417 420 @propertycache
418 421 def dirfoldmap(self):
419 422 f = {}
420 423 normcase = util.normcase
421 424 for name in self._dirs:
422 425 f[normcase(name)] = name
423 426 return f
424 427
425 428 def hastrackeddir(self, d):
426 429 """
427 430 Returns True if the dirstate contains a tracked (not removed) file
428 431 in this directory.
429 432 """
430 433 return d in self._dirs
431 434
432 435 def hasdir(self, d):
433 436 """
434 437 Returns True if the dirstate contains a file (tracked or removed)
435 438 in this directory.
436 439 """
437 440 return d in self._alldirs
438 441
439 442 @propertycache
440 443 def _dirs(self):
441 444 return pathutil.dirs(self._map, only_tracked=True)
442 445
443 446 @propertycache
444 447 def _alldirs(self):
445 448 return pathutil.dirs(self._map)
446 449
447 450 ### code related to manipulation of entries and copy-sources
448 451
449 452 def reset_state(
450 453 self,
451 454 filename,
452 455 wc_tracked=False,
453 456 p1_tracked=False,
454 457 p2_info=False,
455 458 has_meaningful_mtime=True,
456 459 parentfiledata=None,
457 460 ):
458 461 """Set a entry to a given state, diregarding all previous state
459 462
460 463 This is to be used by the part of the dirstate API dedicated to
461 464 adjusting the dirstate after a update/merge.
462 465
463 466 note: calling this might result to no entry existing at all if the
464 467 dirstate map does not see any point at having one for this file
465 468 anymore.
466 469 """
467 470 # copy information are now outdated
468 471 # (maybe new information should be in directly passed to this function)
469 472 self.copymap.pop(filename, None)
470 473
471 474 if not (p1_tracked or p2_info or wc_tracked):
472 475 old_entry = self._map.get(filename)
473 476 self._drop_entry(filename)
474 477 self._dirs_decr(filename, old_entry=old_entry)
475 478 return
476 479
477 480 old_entry = self._map.get(filename)
478 481 self._dirs_incr(filename, old_entry)
479 482 entry = DirstateItem(
480 483 wc_tracked=wc_tracked,
481 484 p1_tracked=p1_tracked,
482 485 p2_info=p2_info,
483 486 has_meaningful_mtime=has_meaningful_mtime,
484 487 parentfiledata=parentfiledata,
485 488 )
486 489 self._map[filename] = entry
487 490
488 491 def set_tracked(self, filename):
489 492 new = False
490 493 entry = self.get(filename)
491 494 if entry is None:
492 495 self._dirs_incr(filename)
493 496 entry = DirstateItem(
494 497 wc_tracked=True,
495 498 )
496 499
497 500 self._map[filename] = entry
498 501 new = True
499 502 elif not entry.tracked:
500 503 self._dirs_incr(filename, entry)
501 504 entry.set_tracked()
502 505 self._refresh_entry(filename, entry)
503 506 new = True
504 507 else:
505 508 # XXX This is probably overkill for more case, but we need this to
506 509 # fully replace the `normallookup` call with `set_tracked` one.
507 510 # Consider smoothing this in the future.
508 511 entry.set_possibly_dirty()
509 512 self._refresh_entry(filename, entry)
510 513 return new
511 514
512 515 def set_untracked(self, f):
513 516 """Mark a file as no longer tracked in the dirstate map"""
514 517 entry = self.get(f)
515 518 if entry is None:
516 519 return False
517 520 else:
518 521 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
519 522 if not entry.p2_info:
520 523 self.copymap.pop(f, None)
521 524 entry.set_untracked()
522 525 self._refresh_entry(f, entry)
523 526 return True
524 527
525 528 def set_clean(self, filename, mode, size, mtime):
526 529 """mark a file as back to a clean state"""
527 530 entry = self[filename]
528 531 size = size & rangemask
529 532 entry.set_clean(mode, size, mtime)
530 533 self._refresh_entry(filename, entry)
531 534 self.copymap.pop(filename, None)
532 535
533 536 def set_possibly_dirty(self, filename):
534 537 """record that the current state of the file on disk is unknown"""
535 538 entry = self[filename]
536 539 entry.set_possibly_dirty()
537 540 self._refresh_entry(filename, entry)
538 541
539 542 def _refresh_entry(self, f, entry):
540 543 """record updated state of an entry"""
541 544 if not entry.any_tracked:
542 545 self._map.pop(f, None)
543 546
544 547 def _drop_entry(self, f):
545 548 """remove any entry for file f
546 549
547 550 This should also drop associated copy information
548 551
549 552 The fact we actually need to drop it is the responsability of the caller"""
550 553 self._map.pop(f, None)
551 554 self.copymap.pop(f, None)
552 555
553 556
554 557 if rustmod is not None:
555 558
556 559 class dirstatemap(_dirstatemapcommon):
557 560
558 561 ### Core data storage and access
559 562
560 563 @propertycache
561 564 def _map(self):
562 565 """
563 566 Fills the Dirstatemap when called.
564 567 """
565 568 # ignore HG_PENDING because identity is used only for writing
566 self.identity = util.filestat.frompath(
567 self._opener.join(self._filename)
568 )
569 self._set_identity()
569 570
570 571 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
571 572 if self._use_dirstate_v2:
572 573 self.docket # load the data if needed
573 574 testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
574 575 if not self.docket.uuid:
575 576 data = b''
576 577 else:
577 578 data = self._read_v2_data()
578 579 self._map = rustmod.DirstateMap.new_v2(
579 580 data, self.docket.data_size, self.docket.tree_metadata
580 581 )
581 582 parents = self.docket.parents
582 583 else:
583 584 self._map, parents = rustmod.DirstateMap.new_v1(
584 585 self._readdirstatefile()
585 586 )
586 587
587 588 if parents and not self._dirtyparents:
588 589 self.setparents(*parents)
589 590
590 591 self.__contains__ = self._map.__contains__
591 592 self.__getitem__ = self._map.__getitem__
592 593 self.get = self._map.get
593 594 return self._map
594 595
595 596 @property
596 597 def copymap(self):
597 598 return self._map.copymap()
598 599
599 600 def debug_iter(self, all):
600 601 """
601 602 Return an iterator of (filename, state, mode, size, mtime) tuples
602 603
603 604 `all`: also include with `state == b' '` dirstate tree nodes that
604 605 don't have an associated `DirstateItem`.
605 606
606 607 """
607 608 return self._map.debug_iter(all)
608 609
609 610 def clear(self):
610 611 self._map.clear()
611 612 self.setparents(
612 613 self._nodeconstants.nullid, self._nodeconstants.nullid
613 614 )
614 615 util.clearcachedproperty(self, b"_dirs")
615 616 util.clearcachedproperty(self, b"_alldirs")
616 617 util.clearcachedproperty(self, b"dirfoldmap")
617 618
618 619 def items(self):
619 620 return self._map.items()
620 621
621 622 # forward for python2,3 compat
622 623 iteritems = items
623 624
624 625 def keys(self):
625 626 return iter(self._map)
626 627
627 628 ### reading/setting parents
628 629
629 630 def setparents(self, p1, p2, fold_p2=False):
630 631 self._parents = (p1, p2)
631 632 self._dirtyparents = True
632 633 copies = {}
633 634 if fold_p2:
634 635 copies = self._map.setparents_fixup()
635 636 return copies
636 637
637 638 ### disk interaction
638 639
639 640 @propertycache
640 641 def identity(self):
641 642 self._map
642 643 return self.identity
643 644
644 645 def write(self, tr, st):
645 646 if not self._use_dirstate_v2:
646 647 p1, p2 = self.parents()
647 648 packed = self._map.write_v1(p1, p2)
648 649 st.write(packed)
649 650 st.close()
650 651 self._dirtyparents = False
651 652 return
652 653
653 654 # We can only append to an existing data file if there is one
654 655 write_mode = self._write_mode
655 656 if self.docket.uuid is None:
656 657 write_mode = WRITE_MODE_FORCE_NEW
657 658 packed, meta, append = self._map.write_v2(write_mode)
658 659 if append:
659 660 docket = self.docket
660 661 data_filename = docket.data_filename()
661 662 with self._opener(data_filename, b'r+b') as fp:
662 663 fp.seek(docket.data_size)
663 664 assert fp.tell() == docket.data_size
664 665 written = fp.write(packed)
665 666 if written is not None: # py2 may return None
666 667 assert written == len(packed), (written, len(packed))
667 668 docket.data_size += len(packed)
668 669 docket.parents = self.parents()
669 670 docket.tree_metadata = meta
670 671 st.write(docket.serialize())
671 672 st.close()
672 673 else:
673 674 self.write_v2_no_append(tr, st, meta, packed)
674 675 # Reload from the newly-written file
675 676 util.clearcachedproperty(self, b"_map")
676 677 self._dirtyparents = False
677 678
678 679 ### code related to maintaining and accessing "extra" property
679 680 # (e.g. "has_dir")
680 681
681 682 @propertycache
682 683 def filefoldmap(self):
683 684 """Returns a dictionary mapping normalized case paths to their
684 685 non-normalized versions.
685 686 """
686 687 return self._map.filefoldmapasdict()
687 688
688 689 def hastrackeddir(self, d):
689 690 return self._map.hastrackeddir(d)
690 691
691 692 def hasdir(self, d):
692 693 return self._map.hasdir(d)
693 694
694 695 @propertycache
695 696 def dirfoldmap(self):
696 697 f = {}
697 698 normcase = util.normcase
698 699 for name in self._map.tracked_dirs():
699 700 f[normcase(name)] = name
700 701 return f
701 702
702 703 ### code related to manipulation of entries and copy-sources
703 704
704 705 def set_tracked(self, f):
705 706 return self._map.set_tracked(f)
706 707
707 708 def set_untracked(self, f):
708 709 return self._map.set_untracked(f)
709 710
710 711 def set_clean(self, filename, mode, size, mtime):
711 712 self._map.set_clean(filename, mode, size, mtime)
712 713
713 714 def set_possibly_dirty(self, f):
714 715 self._map.set_possibly_dirty(f)
715 716
716 717 def reset_state(
717 718 self,
718 719 filename,
719 720 wc_tracked=False,
720 721 p1_tracked=False,
721 722 p2_info=False,
722 723 has_meaningful_mtime=True,
723 724 parentfiledata=None,
724 725 ):
725 726 return self._map.reset_state(
726 727 filename,
727 728 wc_tracked,
728 729 p1_tracked,
729 730 p2_info,
730 731 has_meaningful_mtime,
731 732 parentfiledata,
732 733 )
General Comments 0
You need to be logged in to leave comments. Login now