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