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