comparison mercurial/context.py @ 40736:d98fb3f42f33

context: floor adjustlinkrev graph walk during copy tracing The `_adjustlinkrev` method gains an optional "stoprev" argument. The linkrev adjustment will give up once this floor is reached. The relevant functions using `_adjustlinkrev` are updated to pass an appropriate value in the copy tracing code. In some private repository, about 10% of the status call triggered the pathological case addressed by this change. The speedup varies from one call to another, the best-observed win is moving from 170s to 11s. The effect of this change can be seen in the public pypy repository, running the following command: hg perftracecopies --source 83c9ff0c0206 --destination 59c79103d5b0 before: 3.401753 seconds after: 2.634897 seconds (-23%)
author Boris Feld <boris.feld@octobus.net>
date Wed, 10 Oct 2018 00:50:37 +0200
parents 69206452a2ac
children cb372d09d30a
comparison
equal deleted inserted replaced
40735:69206452a2ac 40736:d98fb3f42f33
710 or self.size() == fctx.size()): 710 or self.size() == fctx.size()):
711 return self._filelog.cmp(self._filenode, fctx.data()) 711 return self._filelog.cmp(self._filenode, fctx.data())
712 712
713 return True 713 return True
714 714
715 def _adjustlinkrev(self, srcrev, inclusive=False): 715 def _adjustlinkrev(self, srcrev, inclusive=False, stoprev=None):
716 """return the first ancestor of <srcrev> introducing <fnode> 716 """return the first ancestor of <srcrev> introducing <fnode>
717 717
718 If the linkrev of the file revision does not point to an ancestor of 718 If the linkrev of the file revision does not point to an ancestor of
719 srcrev, we'll walk down the ancestors until we find one introducing 719 srcrev, we'll walk down the ancestors until we find one introducing
720 this file revision. 720 this file revision.
721 721
722 :srcrev: the changeset revision we search ancestors from 722 :srcrev: the changeset revision we search ancestors from
723 :inclusive: if true, the src revision will also be checked 723 :inclusive: if true, the src revision will also be checked
724 :stoprev: an optional revision to stop the walk at. If no introduction
725 of this file content could be found before this floor
726 revision, the function will returns "None" and stops its
727 iteration.
724 """ 728 """
725 repo = self._repo 729 repo = self._repo
726 cl = repo.unfiltered().changelog 730 cl = repo.unfiltered().changelog
727 mfl = repo.manifestlog 731 mfl = repo.manifestlog
728 # fetch the linkrev 732 # fetch the linkrev
746 if iteranc is None: 750 if iteranc is None:
747 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive) 751 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
748 fnode = self._filenode 752 fnode = self._filenode
749 path = self._path 753 path = self._path
750 for a in iteranc: 754 for a in iteranc:
755 if stoprev is not None and a < stoprev:
756 return None
751 ac = cl.read(a) # get changeset data (we avoid object creation) 757 ac = cl.read(a) # get changeset data (we avoid object creation)
752 if path in ac[3]: # checking the 'files' field. 758 if path in ac[3]: # checking the 'files' field.
753 # The file has been touched, check if the content is 759 # The file has been touched, check if the content is
754 # similar to the one we search for. 760 # similar to the one we search for.
755 if fnode == mfl[ac[0]].readfast().get(path): 761 if fnode == mfl[ac[0]].readfast().get(path):
763 def isintroducedafter(self, changelogrev): 769 def isintroducedafter(self, changelogrev):
764 """True if a filectx has been introduced after a given floor revision 770 """True if a filectx has been introduced after a given floor revision
765 """ 771 """
766 if self.linkrev() >= changelogrev: 772 if self.linkrev() >= changelogrev:
767 return True 773 return True
768 introrev = self._introrev() 774 introrev = self._introrev(stoprev=changelogrev)
775 if introrev is None:
776 return False
769 return introrev >= changelogrev 777 return introrev >= changelogrev
770 778
771 def introrev(self): 779 def introrev(self):
772 """return the rev of the changeset which introduced this file revision 780 """return the rev of the changeset which introduced this file revision
773 781
777 'linkrev-shadowing' when a file revision is used by multiple 785 'linkrev-shadowing' when a file revision is used by multiple
778 changesets. 786 changesets.
779 """ 787 """
780 return self._introrev() 788 return self._introrev()
781 789
782 def _introrev(self): 790 def _introrev(self, stoprev=None):
791 """
792 Same as `introrev` but, with an extra argument to limit changelog
793 iteration range in some internal usecase.
794
795 If `stoprev` is set, the `introrev` will not be searched past that
796 `stoprev` revision and "None" might be returned. This is useful to
797 limit the iteration range.
798 """
783 toprev = None 799 toprev = None
784 attrs = vars(self) 800 attrs = vars(self)
785 if r'_changeid' in attrs: 801 if r'_changeid' in attrs:
786 # We have a cached value already 802 # We have a cached value already
787 toprev = self._changeid 803 toprev = self._changeid
788 elif r'_changectx' in attrs: 804 elif r'_changectx' in attrs:
789 # We know which changelog entry we are coming from 805 # We know which changelog entry we are coming from
790 toprev = self._changectx.rev() 806 toprev = self._changectx.rev()
791 807
792 if toprev is not None: 808 if toprev is not None:
793 return self._adjustlinkrev(toprev, inclusive=True) 809 return self._adjustlinkrev(toprev, inclusive=True, stoprev=stoprev)
794 elif r'_descendantrev' in attrs: 810 elif r'_descendantrev' in attrs:
795 introrev = self._adjustlinkrev(self._descendantrev) 811 introrev = self._adjustlinkrev(self._descendantrev, stoprev=stoprev)
796 # be nice and cache the result of the computation 812 # be nice and cache the result of the computation
797 self._changeid = introrev 813 if introrev is not None:
814 self._changeid = introrev
798 return introrev 815 return introrev
799 else: 816 else:
800 return self.linkrev() 817 return self.linkrev()
801 818
802 def introfilectx(self): 819 def introfilectx(self):