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