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