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