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