##// END OF EJS Templates
dirstatemap: use a common implement for set_tracked...
marmoute -
r48941:a39192fc default
parent child Browse files
Show More
@@ -1,951 +1,937 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 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 82 ### sub-class utility method
83 83 #
84 84 # Use to allow for generic implementation of some method while still coping
85 85 # with minor difference between implementation.
86 86
87 87 def _dirs_incr(self, filename, old_entry=None):
88 88 """incremente the dirstate counter if applicable
89 89
90 90 This might be a no-op for some subclass who deal with directory
91 91 tracking in a different way.
92 92 """
93 93
94 94 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
95 95 """decremente the dirstate counter if applicable
96 96
97 97 This might be a no-op for some subclass who deal with directory
98 98 tracking in a different way.
99 99 """
100 100
101 101 def _refresh_entry(self, f, entry):
102 102 """record updated state of an entry"""
103 103
104 104 def _insert_entry(self, f, entry):
105 105 """add a new dirstate entry (or replace an unrelated one)
106 106
107 107 The fact it is actually new is the responsability of the caller
108 108 """
109 109
110 110 ### method to manipulate the entries
111 111
112 def set_tracked(self, filename):
113 new = False
114 entry = self.get(filename)
115 if entry is None:
116 self._dirs_incr(filename)
117 entry = DirstateItem(
118 p1_tracked=False,
119 p2_tracked=False,
120 wc_tracked=True,
121 merged=False,
122 clean_p1=False,
123 clean_p2=False,
124 possibly_dirty=False,
125 parentfiledata=None,
126 )
127
128 self._insert_entry(filename, entry)
129 new = True
130 elif not entry.tracked:
131 self._dirs_incr(filename, entry)
132 entry.set_tracked()
133 self._refresh_entry(filename, entry)
134 new = True
135 else:
136 # XXX This is probably overkill for more case, but we need this to
137 # fully replace the `normallookup` call with `set_tracked` one.
138 # Consider smoothing this in the future.
139 entry.set_possibly_dirty()
140 self._refresh_entry(filename, entry)
141 return new
142
112 143 def set_untracked(self, f):
113 144 """Mark a file as no longer tracked in the dirstate map"""
114 145 entry = self.get(f)
115 146 if entry is None:
116 147 return False
117 148 else:
118 149 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
119 150 if not entry.merged:
120 151 self.copymap.pop(f, None)
121 152 entry.set_untracked()
122 153 self._refresh_entry(f, entry)
123 154 return True
124 155
125 156
126 157 class dirstatemap(_dirstatemapcommon):
127 158 """Map encapsulating the dirstate's contents.
128 159
129 160 The dirstate contains the following state:
130 161
131 162 - `identity` is the identity of the dirstate file, which can be used to
132 163 detect when changes have occurred to the dirstate file.
133 164
134 165 - `parents` is a pair containing the parents of the working copy. The
135 166 parents are updated by calling `setparents`.
136 167
137 168 - the state map maps filenames to tuples of (state, mode, size, mtime),
138 169 where state is a single character representing 'normal', 'added',
139 170 'removed', or 'merged'. It is read by treating the dirstate as a
140 171 dict. File state is updated by calling various methods (see each
141 172 documentation for details):
142 173
143 174 - `reset_state`,
144 175 - `set_tracked`
145 176 - `set_untracked`
146 177 - `set_clean`
147 178 - `set_possibly_dirty`
148 179
149 180 - `copymap` maps destination filenames to their source filename.
150 181
151 182 The dirstate also provides the following views onto the state:
152 183
153 184 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
154 185 form that they appear as in the dirstate.
155 186
156 187 - `dirfoldmap` is a dict mapping normalized directory names to the
157 188 denormalized form that they appear as in the dirstate.
158 189 """
159 190
160 191 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
161 192 super(dirstatemap, self).__init__(
162 193 ui, opener, root, nodeconstants, use_dirstate_v2
163 194 )
164 195 if self._use_dirstate_v2:
165 196 msg = "Dirstate V2 not supportedi"
166 197 msg += "(should have detected unsupported requirement)"
167 198 raise error.ProgrammingError(msg)
168 199
169 200 ### Core data storage and access
170 201
171 202 @propertycache
172 203 def _map(self):
173 204 self._map = {}
174 205 self.read()
175 206 return self._map
176 207
177 208 @propertycache
178 209 def copymap(self):
179 210 self.copymap = {}
180 211 self._map
181 212 return self.copymap
182 213
183 214 def clear(self):
184 215 self._map.clear()
185 216 self.copymap.clear()
186 217 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
187 218 util.clearcachedproperty(self, b"_dirs")
188 219 util.clearcachedproperty(self, b"_alldirs")
189 220 util.clearcachedproperty(self, b"filefoldmap")
190 221 util.clearcachedproperty(self, b"dirfoldmap")
191 222
192 223 def items(self):
193 224 return pycompat.iteritems(self._map)
194 225
195 226 # forward for python2,3 compat
196 227 iteritems = items
197 228
198 229 def debug_iter(self, all):
199 230 """
200 231 Return an iterator of (filename, state, mode, size, mtime) tuples
201 232
202 233 `all` is unused when Rust is not enabled
203 234 """
204 235 for (filename, item) in self.items():
205 236 yield (filename, item.state, item.mode, item.size, item.mtime)
206 237
207 238 def keys(self):
208 239 return self._map.keys()
209 240
210 241 ### reading/setting parents
211 242
212 243 def parents(self):
213 244 if not self._parents:
214 245 try:
215 246 fp = self._opendirstatefile()
216 247 st = fp.read(2 * self._nodelen)
217 248 fp.close()
218 249 except IOError as err:
219 250 if err.errno != errno.ENOENT:
220 251 raise
221 252 # File doesn't exist, so the current state is empty
222 253 st = b''
223 254
224 255 l = len(st)
225 256 if l == self._nodelen * 2:
226 257 self._parents = (
227 258 st[: self._nodelen],
228 259 st[self._nodelen : 2 * self._nodelen],
229 260 )
230 261 elif l == 0:
231 262 self._parents = (
232 263 self._nodeconstants.nullid,
233 264 self._nodeconstants.nullid,
234 265 )
235 266 else:
236 267 raise error.Abort(
237 268 _(b'working directory state appears damaged!')
238 269 )
239 270
240 271 return self._parents
241 272
242 273 def setparents(self, p1, p2, fold_p2=False):
243 274 self._parents = (p1, p2)
244 275 self._dirtyparents = True
245 276 copies = {}
246 277 if fold_p2:
247 278 for f, s in pycompat.iteritems(self._map):
248 279 # Discard "merged" markers when moving away from a merge state
249 280 if s.merged or s.from_p2:
250 281 source = self.copymap.pop(f, None)
251 282 if source:
252 283 copies[f] = source
253 284 s.drop_merge_data()
254 285 return copies
255 286
256 287 ### disk interaction
257 288
258 289 def read(self):
259 290 # ignore HG_PENDING because identity is used only for writing
260 291 self.identity = util.filestat.frompath(
261 292 self._opener.join(self._filename)
262 293 )
263 294
264 295 try:
265 296 fp = self._opendirstatefile()
266 297 try:
267 298 st = fp.read()
268 299 finally:
269 300 fp.close()
270 301 except IOError as err:
271 302 if err.errno != errno.ENOENT:
272 303 raise
273 304 return
274 305 if not st:
275 306 return
276 307
277 308 if util.safehasattr(parsers, b'dict_new_presized'):
278 309 # Make an estimate of the number of files in the dirstate based on
279 310 # its size. This trades wasting some memory for avoiding costly
280 311 # resizes. Each entry have a prefix of 17 bytes followed by one or
281 312 # two path names. Studies on various large-scale real-world repositories
282 313 # found 54 bytes a reasonable upper limit for the average path names.
283 314 # Copy entries are ignored for the sake of this estimate.
284 315 self._map = parsers.dict_new_presized(len(st) // 71)
285 316
286 317 # Python's garbage collector triggers a GC each time a certain number
287 318 # of container objects (the number being defined by
288 319 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
289 320 # for each file in the dirstate. The C version then immediately marks
290 321 # them as not to be tracked by the collector. However, this has no
291 322 # effect on when GCs are triggered, only on what objects the GC looks
292 323 # into. This means that O(number of files) GCs are unavoidable.
293 324 # Depending on when in the process's lifetime the dirstate is parsed,
294 325 # this can get very expensive. As a workaround, disable GC while
295 326 # parsing the dirstate.
296 327 #
297 328 # (we cannot decorate the function directly since it is in a C module)
298 329 parse_dirstate = util.nogc(parsers.parse_dirstate)
299 330 p = parse_dirstate(self._map, self.copymap, st)
300 331 if not self._dirtyparents:
301 332 self.setparents(*p)
302 333
303 334 # Avoid excess attribute lookups by fast pathing certain checks
304 335 self.__contains__ = self._map.__contains__
305 336 self.__getitem__ = self._map.__getitem__
306 337 self.get = self._map.get
307 338
308 339 def write(self, _tr, st, now):
309 340 d = parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
310 341 st.write(d)
311 342 st.close()
312 343 self._dirtyparents = False
313 344
314 345 def _opendirstatefile(self):
315 346 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
316 347 if self._pendingmode is not None and self._pendingmode != mode:
317 348 fp.close()
318 349 raise error.Abort(
319 350 _(b'working directory state may be changed parallelly')
320 351 )
321 352 self._pendingmode = mode
322 353 return fp
323 354
324 355 @propertycache
325 356 def identity(self):
326 357 self._map
327 358 return self.identity
328 359
329 360 ### code related to maintaining and accessing "extra" property
330 361 # (e.g. "has_dir")
331 362
332 363 def _dirs_incr(self, filename, old_entry=None):
333 364 """incremente the dirstate counter if applicable"""
334 365 if (
335 366 old_entry is None or old_entry.removed
336 367 ) and "_dirs" in self.__dict__:
337 368 self._dirs.addpath(filename)
338 369 if old_entry is None and "_alldirs" in self.__dict__:
339 370 self._alldirs.addpath(filename)
340 371
341 372 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
342 373 """decremente the dirstate counter if applicable"""
343 374 if old_entry is not None:
344 375 if "_dirs" in self.__dict__ and not old_entry.removed:
345 376 self._dirs.delpath(filename)
346 377 if "_alldirs" in self.__dict__ and not remove_variant:
347 378 self._alldirs.delpath(filename)
348 379 elif remove_variant and "_alldirs" in self.__dict__:
349 380 self._alldirs.addpath(filename)
350 381 if "filefoldmap" in self.__dict__:
351 382 normed = util.normcase(filename)
352 383 self.filefoldmap.pop(normed, None)
353 384
354 385 @propertycache
355 386 def filefoldmap(self):
356 387 """Returns a dictionary mapping normalized case paths to their
357 388 non-normalized versions.
358 389 """
359 390 try:
360 391 makefilefoldmap = parsers.make_file_foldmap
361 392 except AttributeError:
362 393 pass
363 394 else:
364 395 return makefilefoldmap(
365 396 self._map, util.normcasespec, util.normcasefallback
366 397 )
367 398
368 399 f = {}
369 400 normcase = util.normcase
370 401 for name, s in pycompat.iteritems(self._map):
371 402 if not s.removed:
372 403 f[normcase(name)] = name
373 404 f[b'.'] = b'.' # prevents useless util.fspath() invocation
374 405 return f
375 406
376 407 @propertycache
377 408 def dirfoldmap(self):
378 409 f = {}
379 410 normcase = util.normcase
380 411 for name in self._dirs:
381 412 f[normcase(name)] = name
382 413 return f
383 414
384 415 def hastrackeddir(self, d):
385 416 """
386 417 Returns True if the dirstate contains a tracked (not removed) file
387 418 in this directory.
388 419 """
389 420 return d in self._dirs
390 421
391 422 def hasdir(self, d):
392 423 """
393 424 Returns True if the dirstate contains a file (tracked or removed)
394 425 in this directory.
395 426 """
396 427 return d in self._alldirs
397 428
398 429 @propertycache
399 430 def _dirs(self):
400 431 return pathutil.dirs(self._map, only_tracked=True)
401 432
402 433 @propertycache
403 434 def _alldirs(self):
404 435 return pathutil.dirs(self._map)
405 436
406 437 ### code related to manipulation of entries and copy-sources
407 438
408 439 def _refresh_entry(self, f, entry):
409 440 if not entry.any_tracked:
410 441 self._map.pop(f, None)
411 442
412 443 def _insert_entry(self, f, entry):
413 444 self._map[f] = entry
414 445
415 446 def set_possibly_dirty(self, filename):
416 447 """record that the current state of the file on disk is unknown"""
417 448 self[filename].set_possibly_dirty()
418 449
419 450 def set_clean(self, filename, mode, size, mtime):
420 451 """mark a file as back to a clean state"""
421 452 entry = self[filename]
422 453 mtime = mtime & rangemask
423 454 size = size & rangemask
424 455 entry.set_clean(mode, size, mtime)
425 456 self.copymap.pop(filename, None)
426 457
427 458 def reset_state(
428 459 self,
429 460 filename,
430 461 wc_tracked=False,
431 462 p1_tracked=False,
432 463 p2_tracked=False,
433 464 merged=False,
434 465 clean_p1=False,
435 466 clean_p2=False,
436 467 possibly_dirty=False,
437 468 parentfiledata=None,
438 469 ):
439 470 """Set a entry to a given state, diregarding all previous state
440 471
441 472 This is to be used by the part of the dirstate API dedicated to
442 473 adjusting the dirstate after a update/merge.
443 474
444 475 note: calling this might result to no entry existing at all if the
445 476 dirstate map does not see any point at having one for this file
446 477 anymore.
447 478 """
448 479 if merged and (clean_p1 or clean_p2):
449 480 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
450 481 raise error.ProgrammingError(msg)
451 482 # copy information are now outdated
452 483 # (maybe new information should be in directly passed to this function)
453 484 self.copymap.pop(filename, None)
454 485
455 486 if not (p1_tracked or p2_tracked or wc_tracked):
456 487 old_entry = self._map.pop(filename, None)
457 488 self._dirs_decr(filename, old_entry=old_entry)
458 489 self.copymap.pop(filename, None)
459 490 return
460 491 elif merged:
461 492 pass
462 493 elif not (p1_tracked or p2_tracked) and wc_tracked:
463 494 pass # file is added, nothing special to adjust
464 495 elif (p1_tracked or p2_tracked) and not wc_tracked:
465 496 pass
466 497 elif clean_p2 and wc_tracked:
467 498 pass
468 499 elif not p1_tracked and p2_tracked and wc_tracked:
469 500 clean_p2 = True
470 501 elif possibly_dirty:
471 502 pass
472 503 elif wc_tracked:
473 504 # this is a "normal" file
474 505 if parentfiledata is None:
475 506 msg = b'failed to pass parentfiledata for a normal file: %s'
476 507 msg %= filename
477 508 raise error.ProgrammingError(msg)
478 509 else:
479 510 assert False, 'unreachable'
480 511
481 512 old_entry = self._map.get(filename)
482 513 self._dirs_incr(filename, old_entry)
483 514 entry = DirstateItem(
484 515 wc_tracked=wc_tracked,
485 516 p1_tracked=p1_tracked,
486 517 p2_tracked=p2_tracked,
487 518 merged=merged,
488 519 clean_p1=clean_p1,
489 520 clean_p2=clean_p2,
490 521 possibly_dirty=possibly_dirty,
491 522 parentfiledata=parentfiledata,
492 523 )
493 524 self._map[filename] = entry
494 525
495 def set_tracked(self, filename):
496 new = False
497 entry = self.get(filename)
498 if entry is None:
499 self._dirs_incr(filename)
500 entry = DirstateItem(
501 p1_tracked=False,
502 p2_tracked=False,
503 wc_tracked=True,
504 merged=False,
505 clean_p1=False,
506 clean_p2=False,
507 possibly_dirty=False,
508 parentfiledata=None,
509 )
510 self._map[filename] = entry
511 new = True
512 elif not entry.tracked:
513 self._dirs_incr(filename, entry)
514 entry.set_tracked()
515 new = True
516 else:
517 # XXX This is probably overkill for more case, but we need this to
518 # fully replace the `normallookup` call with `set_tracked` one.
519 # Consider smoothing this in the future.
520 self.set_possibly_dirty(filename)
521 return new
522
523 526
524 527 if rustmod is not None:
525 528
526 529 class dirstatemap(_dirstatemapcommon):
527 530 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
528 531 super(dirstatemap, self).__init__(
529 532 ui, opener, root, nodeconstants, use_dirstate_v2
530 533 )
531 534 self._docket = None
532 535
533 536 ### Core data storage and access
534 537
535 538 @property
536 539 def docket(self):
537 540 if not self._docket:
538 541 if not self._use_dirstate_v2:
539 542 raise error.ProgrammingError(
540 543 b'dirstate only has a docket in v2 format'
541 544 )
542 545 self._docket = docketmod.DirstateDocket.parse(
543 546 self._readdirstatefile(), self._nodeconstants
544 547 )
545 548 return self._docket
546 549
547 550 @propertycache
548 551 def _map(self):
549 552 """
550 553 Fills the Dirstatemap when called.
551 554 """
552 555 # ignore HG_PENDING because identity is used only for writing
553 556 self.identity = util.filestat.frompath(
554 557 self._opener.join(self._filename)
555 558 )
556 559
557 560 if self._use_dirstate_v2:
558 561 if self.docket.uuid:
559 562 # TODO: use mmap when possible
560 563 data = self._opener.read(self.docket.data_filename())
561 564 else:
562 565 data = b''
563 566 self._map = rustmod.DirstateMap.new_v2(
564 567 data, self.docket.data_size, self.docket.tree_metadata
565 568 )
566 569 parents = self.docket.parents
567 570 else:
568 571 self._map, parents = rustmod.DirstateMap.new_v1(
569 572 self._readdirstatefile()
570 573 )
571 574
572 575 if parents and not self._dirtyparents:
573 576 self.setparents(*parents)
574 577
575 578 self.__contains__ = self._map.__contains__
576 579 self.__getitem__ = self._map.__getitem__
577 580 self.get = self._map.get
578 581 return self._map
579 582
580 583 @property
581 584 def copymap(self):
582 585 return self._map.copymap()
583 586
584 587 def debug_iter(self, all):
585 588 """
586 589 Return an iterator of (filename, state, mode, size, mtime) tuples
587 590
588 591 `all`: also include with `state == b' '` dirstate tree nodes that
589 592 don't have an associated `DirstateItem`.
590 593
591 594 """
592 595 return self._map.debug_iter(all)
593 596
594 597 def clear(self):
595 598 self._map.clear()
596 599 self.setparents(
597 600 self._nodeconstants.nullid, self._nodeconstants.nullid
598 601 )
599 602 util.clearcachedproperty(self, b"_dirs")
600 603 util.clearcachedproperty(self, b"_alldirs")
601 604 util.clearcachedproperty(self, b"dirfoldmap")
602 605
603 606 def items(self):
604 607 return self._map.items()
605 608
606 609 # forward for python2,3 compat
607 610 iteritems = items
608 611
609 612 def keys(self):
610 613 return iter(self._map)
611 614
612 615 ### reading/setting parents
613 616
614 617 def setparents(self, p1, p2, fold_p2=False):
615 618 self._parents = (p1, p2)
616 619 self._dirtyparents = True
617 620 copies = {}
618 621 if fold_p2:
619 622 # Collect into an intermediate list to avoid a `RuntimeError`
620 623 # exception due to mutation during iteration.
621 624 # TODO: move this the whole loop to Rust where `iter_mut`
622 625 # enables in-place mutation of elements of a collection while
623 626 # iterating it, without mutating the collection itself.
624 627 candidatefiles = [
625 628 (f, s)
626 629 for f, s in self._map.items()
627 630 if s.merged or s.from_p2
628 631 ]
629 632 for f, s in candidatefiles:
630 633 # Discard "merged" markers when moving away from a merge state
631 634 if s.merged:
632 635 source = self.copymap.get(f)
633 636 if source:
634 637 copies[f] = source
635 638 self.reset_state(
636 639 f,
637 640 wc_tracked=True,
638 641 p1_tracked=True,
639 642 possibly_dirty=True,
640 643 )
641 644 # Also fix up otherparent markers
642 645 elif s.from_p2:
643 646 source = self.copymap.get(f)
644 647 if source:
645 648 copies[f] = source
646 649 self.reset_state(
647 650 f,
648 651 p1_tracked=False,
649 652 wc_tracked=True,
650 653 )
651 654 return copies
652 655
653 656 def parents(self):
654 657 if not self._parents:
655 658 if self._use_dirstate_v2:
656 659 self._parents = self.docket.parents
657 660 else:
658 661 read_len = self._nodelen * 2
659 662 st = self._readdirstatefile(read_len)
660 663 l = len(st)
661 664 if l == read_len:
662 665 self._parents = (
663 666 st[: self._nodelen],
664 667 st[self._nodelen : 2 * self._nodelen],
665 668 )
666 669 elif l == 0:
667 670 self._parents = (
668 671 self._nodeconstants.nullid,
669 672 self._nodeconstants.nullid,
670 673 )
671 674 else:
672 675 raise error.Abort(
673 676 _(b'working directory state appears damaged!')
674 677 )
675 678
676 679 return self._parents
677 680
678 681 ### disk interaction
679 682
680 683 @propertycache
681 684 def identity(self):
682 685 self._map
683 686 return self.identity
684 687
685 688 def write(self, tr, st, now):
686 689 if not self._use_dirstate_v2:
687 690 p1, p2 = self.parents()
688 691 packed = self._map.write_v1(p1, p2, now)
689 692 st.write(packed)
690 693 st.close()
691 694 self._dirtyparents = False
692 695 return
693 696
694 697 # We can only append to an existing data file if there is one
695 698 can_append = self.docket.uuid is not None
696 699 packed, meta, append = self._map.write_v2(now, can_append)
697 700 if append:
698 701 docket = self.docket
699 702 data_filename = docket.data_filename()
700 703 if tr:
701 704 tr.add(data_filename, docket.data_size)
702 705 with self._opener(data_filename, b'r+b') as fp:
703 706 fp.seek(docket.data_size)
704 707 assert fp.tell() == docket.data_size
705 708 written = fp.write(packed)
706 709 if written is not None: # py2 may return None
707 710 assert written == len(packed), (written, len(packed))
708 711 docket.data_size += len(packed)
709 712 docket.parents = self.parents()
710 713 docket.tree_metadata = meta
711 714 st.write(docket.serialize())
712 715 st.close()
713 716 else:
714 717 old_docket = self.docket
715 718 new_docket = docketmod.DirstateDocket.with_new_uuid(
716 719 self.parents(), len(packed), meta
717 720 )
718 721 data_filename = new_docket.data_filename()
719 722 if tr:
720 723 tr.add(data_filename, 0)
721 724 self._opener.write(data_filename, packed)
722 725 # Write the new docket after the new data file has been
723 726 # written. Because `st` was opened with `atomictemp=True`,
724 727 # the actual `.hg/dirstate` file is only affected on close.
725 728 st.write(new_docket.serialize())
726 729 st.close()
727 730 # Remove the old data file after the new docket pointing to
728 731 # the new data file was written.
729 732 if old_docket.uuid:
730 733 data_filename = old_docket.data_filename()
731 734 unlink = lambda _tr=None: self._opener.unlink(data_filename)
732 735 if tr:
733 736 category = b"dirstate-v2-clean-" + old_docket.uuid
734 737 tr.addpostclose(category, unlink)
735 738 else:
736 739 unlink()
737 740 self._docket = new_docket
738 741 # Reload from the newly-written file
739 742 util.clearcachedproperty(self, b"_map")
740 743 self._dirtyparents = False
741 744
742 745 def _opendirstatefile(self):
743 746 fp, mode = txnutil.trypending(
744 747 self._root, self._opener, self._filename
745 748 )
746 749 if self._pendingmode is not None and self._pendingmode != mode:
747 750 fp.close()
748 751 raise error.Abort(
749 752 _(b'working directory state may be changed parallelly')
750 753 )
751 754 self._pendingmode = mode
752 755 return fp
753 756
754 757 def _readdirstatefile(self, size=-1):
755 758 try:
756 759 with self._opendirstatefile() as fp:
757 760 return fp.read(size)
758 761 except IOError as err:
759 762 if err.errno != errno.ENOENT:
760 763 raise
761 764 # File doesn't exist, so the current state is empty
762 765 return b''
763 766
764 767 ### code related to maintaining and accessing "extra" property
765 768 # (e.g. "has_dir")
766 769
767 770 @propertycache
768 771 def filefoldmap(self):
769 772 """Returns a dictionary mapping normalized case paths to their
770 773 non-normalized versions.
771 774 """
772 775 return self._map.filefoldmapasdict()
773 776
774 777 def hastrackeddir(self, d):
775 778 return self._map.hastrackeddir(d)
776 779
777 780 def hasdir(self, d):
778 781 return self._map.hasdir(d)
779 782
780 783 @propertycache
781 784 def dirfoldmap(self):
782 785 f = {}
783 786 normcase = util.normcase
784 787 for name in self._map.tracked_dirs():
785 788 f[normcase(name)] = name
786 789 return f
787 790
788 791 ### code related to manipulation of entries and copy-sources
789 792
790 793 def _refresh_entry(self, f, entry):
791 794 if not entry.any_tracked:
792 795 self._map.drop_item_and_copy_source(f)
793 796 else:
794 797 self._map.addfile(f, entry)
795 798
796 799 def _insert_entry(self, f, entry):
797 800 self._map.addfile(f, entry)
798 801
799 802 def set_possibly_dirty(self, filename):
800 803 """record that the current state of the file on disk is unknown"""
801 804 entry = self[filename]
802 805 entry.set_possibly_dirty()
803 806 self._map.set_dirstate_item(filename, entry)
804 807
805 808 def set_clean(self, filename, mode, size, mtime):
806 809 """mark a file as back to a clean state"""
807 810 entry = self[filename]
808 811 mtime = mtime & rangemask
809 812 size = size & rangemask
810 813 entry.set_clean(mode, size, mtime)
811 814 self._map.set_dirstate_item(filename, entry)
812 815 self._map.copymap().pop(filename, None)
813 816
814 817 def __setitem__(self, key, value):
815 818 assert isinstance(value, DirstateItem)
816 819 self._map.set_dirstate_item(key, value)
817 820
818 821 def reset_state(
819 822 self,
820 823 filename,
821 824 wc_tracked=False,
822 825 p1_tracked=False,
823 826 p2_tracked=False,
824 827 merged=False,
825 828 clean_p1=False,
826 829 clean_p2=False,
827 830 possibly_dirty=False,
828 831 parentfiledata=None,
829 832 ):
830 833 """Set a entry to a given state, disregarding all previous state
831 834
832 835 This is to be used by the part of the dirstate API dedicated to
833 836 adjusting the dirstate after a update/merge.
834 837
835 838 note: calling this might result to no entry existing at all if the
836 839 dirstate map does not see any point at having one for this file
837 840 anymore.
838 841 """
839 842 if merged and (clean_p1 or clean_p2):
840 843 msg = (
841 844 b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
842 845 )
843 846 raise error.ProgrammingError(msg)
844 847 # copy information are now outdated
845 848 # (maybe new information should be in directly passed to this function)
846 849 self.copymap.pop(filename, None)
847 850
848 851 if not (p1_tracked or p2_tracked or wc_tracked):
849 852 self._map.drop_item_and_copy_source(filename)
850 853 elif merged:
851 854 # XXX might be merged and removed ?
852 855 entry = self.get(filename)
853 856 if entry is not None and entry.tracked:
854 857 # XXX mostly replicate dirstate.other parent. We should get
855 858 # the higher layer to pass us more reliable data where `merged`
856 859 # actually mean merged. Dropping the else clause will show
857 860 # failure in `test-graft.t`
858 861 self.addfile(filename, merged=True)
859 862 else:
860 863 self.addfile(filename, from_p2=True)
861 864 elif not (p1_tracked or p2_tracked) and wc_tracked:
862 865 self.addfile(
863 866 filename, added=True, possibly_dirty=possibly_dirty
864 867 )
865 868 elif (p1_tracked or p2_tracked) and not wc_tracked:
866 869 # XXX might be merged and removed ?
867 870 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
868 871 elif clean_p2 and wc_tracked:
869 872 if p1_tracked or self.get(filename) is not None:
870 873 # XXX the `self.get` call is catching some case in
871 874 # `test-merge-remove.t` where the file is tracked in p1, the
872 875 # p1_tracked argument is False.
873 876 #
874 877 # In addition, this seems to be a case where the file is marked
875 878 # as merged without actually being the result of a merge
876 879 # action. So thing are not ideal here.
877 880 self.addfile(filename, merged=True)
878 881 else:
879 882 self.addfile(filename, from_p2=True)
880 883 elif not p1_tracked and p2_tracked and wc_tracked:
881 884 self.addfile(
882 885 filename, from_p2=True, possibly_dirty=possibly_dirty
883 886 )
884 887 elif possibly_dirty:
885 888 self.addfile(filename, possibly_dirty=possibly_dirty)
886 889 elif wc_tracked:
887 890 # this is a "normal" file
888 891 if parentfiledata is None:
889 892 msg = b'failed to pass parentfiledata for a normal file: %s'
890 893 msg %= filename
891 894 raise error.ProgrammingError(msg)
892 895 mode, size, mtime = parentfiledata
893 896 self.addfile(filename, mode=mode, size=size, mtime=mtime)
894 897 else:
895 898 assert False, 'unreachable'
896 899
897 def set_tracked(self, filename):
898 new = False
899 entry = self.get(filename)
900 if entry is None:
901 self.addfile(filename, added=True)
902 new = True
903 elif not entry.tracked:
904 entry.set_tracked()
905 self._map.set_dirstate_item(filename, entry)
906 new = True
907 else:
908 # XXX This is probably overkill for more case, but we need this to
909 # fully replace the `normallookup` call with `set_tracked` one.
910 # Consider smoothing this in the future.
911 self.set_possibly_dirty(filename)
912 return new
913
914 900 ### Legacy method we need to get rid of
915 901
916 902 def addfile(
917 903 self,
918 904 f,
919 905 mode=0,
920 906 size=None,
921 907 mtime=None,
922 908 added=False,
923 909 merged=False,
924 910 from_p2=False,
925 911 possibly_dirty=False,
926 912 ):
927 913 if added:
928 914 assert not possibly_dirty
929 915 assert not from_p2
930 916 item = DirstateItem.new_added()
931 917 elif merged:
932 918 assert not possibly_dirty
933 919 assert not from_p2
934 920 item = DirstateItem.new_merged()
935 921 elif from_p2:
936 922 assert not possibly_dirty
937 923 item = DirstateItem.new_from_p2()
938 924 elif possibly_dirty:
939 925 item = DirstateItem.new_possibly_dirty()
940 926 else:
941 927 assert size is not None
942 928 assert mtime is not None
943 929 size = size & rangemask
944 930 mtime = mtime & rangemask
945 931 item = DirstateItem.new_normal(mode, size, mtime)
946 932 self._map.addfile(f, item)
947 933 if added:
948 934 self.copymap.pop(f, None)
949 935
950 936 def removefile(self, *args, **kwargs):
951 937 return self._map.removefile(*args, **kwargs)
General Comments 0
You need to be logged in to leave comments. Login now