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():