Mercurial > hg
comparison mercurial/dirstatemap.py @ 48061:060cd909439f
dirstate: drop all logic around the "non-normal" sets
The dirstate has a lot of code to compute a set of all "non-normal" and
"from_other_parent" entries.
This is all used in one, unique, location, when `setparent` is called and moved
from a merge to a non merge. At that time, any "merge related" information has
to be dropped. This is mostly useful for command like `graft` or `shelve` that
move to a single-parent state -before- the commit. Otherwise the commit will
already have removed all traces of the merge information in the dirstate (e.g.
for a regular merges).
The bookkeeping for these sets is quite invasive. And it seems simpler to just
drop it and do the full computation in the single location where we actually
use it (since we have to do the computation at least once anyway).
This simplify the code a lot, and clarify why this kind of computation is
needed.
The possible drawback compared to the previous code are:
- if the operation happens in a loop, we will end up doing it multiple time,
- the C code to detect entry of interest have been dropped, for now. It will be
re-introduced later, with a processing code directly in C for even faster
operation.
Differential Revision: https://phab.mercurial-scm.org/D11507
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Tue, 28 Sep 2021 20:05:37 +0200 |
parents | a660d8a53267 |
children | bf8837e3d7ce |
comparison
equal
deleted
inserted
replaced
48060:a660d8a53267 | 48061:060cd909439f |
---|---|
59 - `set_possibly_dirty` | 59 - `set_possibly_dirty` |
60 | 60 |
61 - `copymap` maps destination filenames to their source filename. | 61 - `copymap` maps destination filenames to their source filename. |
62 | 62 |
63 The dirstate also provides the following views onto the state: | 63 The dirstate also provides the following views onto the state: |
64 | |
65 - `nonnormalset` is a set of the filenames that have state other | |
66 than 'normal', or are normal but have an mtime of -1 ('normallookup'). | |
67 | |
68 - `otherparentset` is a set of the filenames that are marked as coming | |
69 from the second parent when the dirstate is currently being merged. | |
70 | 64 |
71 - `filefoldmap` is a dict mapping normalized filenames to the denormalized | 65 - `filefoldmap` is a dict mapping normalized filenames to the denormalized |
72 form that they appear as in the dirstate. | 66 form that they appear as in the dirstate. |
73 | 67 |
74 - `dirfoldmap` is a dict mapping normalized directory names to the | 68 - `dirfoldmap` is a dict mapping normalized directory names to the |
110 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid) | 104 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid) |
111 util.clearcachedproperty(self, b"_dirs") | 105 util.clearcachedproperty(self, b"_dirs") |
112 util.clearcachedproperty(self, b"_alldirs") | 106 util.clearcachedproperty(self, b"_alldirs") |
113 util.clearcachedproperty(self, b"filefoldmap") | 107 util.clearcachedproperty(self, b"filefoldmap") |
114 util.clearcachedproperty(self, b"dirfoldmap") | 108 util.clearcachedproperty(self, b"dirfoldmap") |
115 util.clearcachedproperty(self, b"nonnormalset") | |
116 util.clearcachedproperty(self, b"otherparentset") | |
117 | 109 |
118 def items(self): | 110 def items(self): |
119 return pycompat.iteritems(self._map) | 111 return pycompat.iteritems(self._map) |
120 | 112 |
121 # forward for python2,3 compat | 113 # forward for python2,3 compat |
183 entry = self[filename] | 175 entry = self[filename] |
184 mtime = mtime & rangemask | 176 mtime = mtime & rangemask |
185 size = size & rangemask | 177 size = size & rangemask |
186 entry.set_clean(mode, size, mtime) | 178 entry.set_clean(mode, size, mtime) |
187 self.copymap.pop(filename, None) | 179 self.copymap.pop(filename, None) |
188 self.nonnormalset.discard(filename) | |
189 | 180 |
190 def reset_state( | 181 def reset_state( |
191 self, | 182 self, |
192 filename, | 183 filename, |
193 wc_tracked=False, | 184 wc_tracked=False, |
216 self.copymap.pop(filename, None) | 207 self.copymap.pop(filename, None) |
217 | 208 |
218 if not (p1_tracked or p2_tracked or wc_tracked): | 209 if not (p1_tracked or p2_tracked or wc_tracked): |
219 old_entry = self._map.pop(filename, None) | 210 old_entry = self._map.pop(filename, None) |
220 self._dirs_decr(filename, old_entry=old_entry) | 211 self._dirs_decr(filename, old_entry=old_entry) |
221 self.nonnormalset.discard(filename) | |
222 self.copymap.pop(filename, None) | 212 self.copymap.pop(filename, None) |
223 return | 213 return |
224 elif merged: | 214 elif merged: |
225 # XXX might be merged and removed ? | 215 # XXX might be merged and removed ? |
226 entry = self.get(filename) | 216 entry = self.get(filename) |
269 clean_p1=clean_p1, | 259 clean_p1=clean_p1, |
270 clean_p2=clean_p2, | 260 clean_p2=clean_p2, |
271 possibly_dirty=possibly_dirty, | 261 possibly_dirty=possibly_dirty, |
272 parentfiledata=parentfiledata, | 262 parentfiledata=parentfiledata, |
273 ) | 263 ) |
274 if entry.dm_nonnormal: | |
275 self.nonnormalset.add(filename) | |
276 else: | |
277 self.nonnormalset.discard(filename) | |
278 if entry.dm_otherparent: | |
279 self.otherparentset.add(filename) | |
280 else: | |
281 self.otherparentset.discard(filename) | |
282 self._map[filename] = entry | 264 self._map[filename] = entry |
283 | 265 |
284 def set_tracked(self, filename): | 266 def set_tracked(self, filename): |
285 new = False | 267 new = False |
286 entry = self.get(filename) | 268 entry = self.get(filename) |
295 clean_p2=False, | 277 clean_p2=False, |
296 possibly_dirty=False, | 278 possibly_dirty=False, |
297 parentfiledata=None, | 279 parentfiledata=None, |
298 ) | 280 ) |
299 self._map[filename] = entry | 281 self._map[filename] = entry |
300 if entry.dm_nonnormal: | |
301 self.nonnormalset.add(filename) | |
302 new = True | 282 new = True |
303 elif not entry.tracked: | 283 elif not entry.tracked: |
304 self._dirs_incr(filename, entry) | 284 self._dirs_incr(filename, entry) |
305 entry.set_tracked() | 285 entry.set_tracked() |
306 new = True | 286 new = True |
319 else: | 299 else: |
320 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added) | 300 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added) |
321 if not entry.merged: | 301 if not entry.merged: |
322 self.copymap.pop(f, None) | 302 self.copymap.pop(f, None) |
323 if entry.added: | 303 if entry.added: |
324 self.nonnormalset.discard(f) | |
325 self._map.pop(f, None) | 304 self._map.pop(f, None) |
326 else: | 305 else: |
327 self.nonnormalset.add(f) | |
328 if entry.from_p2: | |
329 self.otherparentset.add(f) | |
330 entry.set_untracked() | 306 entry.set_untracked() |
331 return True | 307 return True |
332 | |
333 def nonnormalentries(self): | |
334 '''Compute the nonnormal dirstate entries from the dmap''' | |
335 try: | |
336 return parsers.nonnormalotherparententries(self._map) | |
337 except AttributeError: | |
338 nonnorm = set() | |
339 otherparent = set() | |
340 for fname, e in pycompat.iteritems(self._map): | |
341 if e.dm_nonnormal: | |
342 nonnorm.add(fname) | |
343 if e.from_p2: | |
344 otherparent.add(fname) | |
345 return nonnorm, otherparent | |
346 | 308 |
347 @propertycache | 309 @propertycache |
348 def filefoldmap(self): | 310 def filefoldmap(self): |
349 """Returns a dictionary mapping normalized case paths to their | 311 """Returns a dictionary mapping normalized case paths to their |
350 non-normalized versions. | 312 non-normalized versions. |
431 def setparents(self, p1, p2, fold_p2=False): | 393 def setparents(self, p1, p2, fold_p2=False): |
432 self._parents = (p1, p2) | 394 self._parents = (p1, p2) |
433 self._dirtyparents = True | 395 self._dirtyparents = True |
434 copies = {} | 396 copies = {} |
435 if fold_p2: | 397 if fold_p2: |
436 candidatefiles = self.non_normal_or_other_parent_paths() | 398 for f, s in pycompat.iteritems(self._map): |
437 | |
438 for f in candidatefiles: | |
439 s = self.get(f) | |
440 if s is None: | |
441 continue | |
442 | |
443 # Discard "merged" markers when moving away from a merge state | 399 # Discard "merged" markers when moving away from a merge state |
444 if s.merged or s.from_p2: | 400 if s.merged or s.from_p2: |
445 source = self.copymap.pop(f, None) | 401 source = self.copymap.pop(f, None) |
446 if source: | 402 if source: |
447 copies[f] = source | 403 copies[f] = source |
502 st.write( | 458 st.write( |
503 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now) | 459 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now) |
504 ) | 460 ) |
505 st.close() | 461 st.close() |
506 self._dirtyparents = False | 462 self._dirtyparents = False |
507 self.nonnormalset, self.otherparentset = self.nonnormalentries() | |
508 | |
509 @propertycache | |
510 def nonnormalset(self): | |
511 nonnorm, otherparents = self.nonnormalentries() | |
512 self.otherparentset = otherparents | |
513 return nonnorm | |
514 | |
515 @propertycache | |
516 def otherparentset(self): | |
517 nonnorm, otherparents = self.nonnormalentries() | |
518 self.nonnormalset = nonnorm | |
519 return otherparents | |
520 | |
521 def non_normal_or_other_parent_paths(self): | |
522 return self.nonnormalset.union(self.otherparentset) | |
523 | 463 |
524 @propertycache | 464 @propertycache |
525 def identity(self): | 465 def identity(self): |
526 self._map | 466 self._map |
527 return self.identity | 467 return self.identity |
641 filename, added=True, possibly_dirty=possibly_dirty | 581 filename, added=True, possibly_dirty=possibly_dirty |
642 ) | 582 ) |
643 elif (p1_tracked or p2_tracked) and not wc_tracked: | 583 elif (p1_tracked or p2_tracked) and not wc_tracked: |
644 # XXX might be merged and removed ? | 584 # XXX might be merged and removed ? |
645 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0) | 585 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0) |
646 self.nonnormalset.add(filename) | |
647 elif clean_p2 and wc_tracked: | 586 elif clean_p2 and wc_tracked: |
648 if p1_tracked or self.get(filename) is not None: | 587 if p1_tracked or self.get(filename) is not None: |
649 # XXX the `self.get` call is catching some case in | 588 # XXX the `self.get` call is catching some case in |
650 # `test-merge-remove.t` where the file is tracked in p1, the | 589 # `test-merge-remove.t` where the file is tracked in p1, the |
651 # p1_tracked argument is False. | 590 # p1_tracked argument is False. |
668 msg = b'failed to pass parentfiledata for a normal file: %s' | 607 msg = b'failed to pass parentfiledata for a normal file: %s' |
669 msg %= filename | 608 msg %= filename |
670 raise error.ProgrammingError(msg) | 609 raise error.ProgrammingError(msg) |
671 mode, size, mtime = parentfiledata | 610 mode, size, mtime = parentfiledata |
672 self.addfile(filename, mode=mode, size=size, mtime=mtime) | 611 self.addfile(filename, mode=mode, size=size, mtime=mtime) |
673 self.nonnormalset.discard(filename) | |
674 else: | 612 else: |
675 assert False, 'unreachable' | 613 assert False, 'unreachable' |
676 | 614 |
677 def set_tracked(self, filename): | 615 def set_tracked(self, filename): |
678 new = False | 616 new = False |
708 return True | 646 return True |
709 | 647 |
710 def removefile(self, *args, **kwargs): | 648 def removefile(self, *args, **kwargs): |
711 return self._rustmap.removefile(*args, **kwargs) | 649 return self._rustmap.removefile(*args, **kwargs) |
712 | 650 |
713 def nonnormalentries(self): | |
714 return self._rustmap.nonnormalentries() | |
715 | |
716 def get(self, *args, **kwargs): | 651 def get(self, *args, **kwargs): |
717 return self._rustmap.get(*args, **kwargs) | 652 return self._rustmap.get(*args, **kwargs) |
718 | 653 |
719 @property | 654 @property |
720 def copymap(self): | 655 def copymap(self): |
788 def setparents(self, p1, p2, fold_p2=False): | 723 def setparents(self, p1, p2, fold_p2=False): |
789 self._parents = (p1, p2) | 724 self._parents = (p1, p2) |
790 self._dirtyparents = True | 725 self._dirtyparents = True |
791 copies = {} | 726 copies = {} |
792 if fold_p2: | 727 if fold_p2: |
793 candidatefiles = self.non_normal_or_other_parent_paths() | 728 # Collect into an intermediate list to avoid a `RuntimeError` |
794 | 729 # exception due to mutation during iteration. |
795 for f in candidatefiles: | 730 # TODO: move this the whole loop to Rust where `iter_mut` |
796 s = self.get(f) | 731 # enables in-place mutation of elements of a collection while |
797 if s is None: | 732 # iterating it, without mutating the collection itself. |
798 continue | 733 candidatefiles = [ |
799 | 734 (f, s) |
735 for f, s in self._rustmap.items() | |
736 if s.merged or s.from_p2 | |
737 ] | |
738 for f, s in candidatefiles: | |
800 # Discard "merged" markers when moving away from a merge state | 739 # Discard "merged" markers when moving away from a merge state |
801 if s.merged: | 740 if s.merged: |
802 source = self.copymap.get(f) | 741 source = self.copymap.get(f) |
803 if source: | 742 if source: |
804 copies[f] = source | 743 copies[f] = source |
963 @propertycache | 902 @propertycache |
964 def identity(self): | 903 def identity(self): |
965 self._rustmap | 904 self._rustmap |
966 return self.identity | 905 return self.identity |
967 | 906 |
968 @property | |
969 def nonnormalset(self): | |
970 nonnorm = self._rustmap.non_normal_entries() | |
971 return nonnorm | |
972 | |
973 @propertycache | |
974 def otherparentset(self): | |
975 otherparents = self._rustmap.other_parent_entries() | |
976 return otherparents | |
977 | |
978 def non_normal_or_other_parent_paths(self): | |
979 return self._rustmap.non_normal_or_other_parent_paths() | |
980 | |
981 @propertycache | 907 @propertycache |
982 def dirfoldmap(self): | 908 def dirfoldmap(self): |
983 f = {} | 909 f = {} |
984 normcase = util.normcase | 910 normcase = util.normcase |
985 for name in self._rustmap.tracked_dirs(): | 911 for name in self._rustmap.tracked_dirs(): |