comparison mercurial/revset.py @ 23719:34364a4b25eb

linkrev: work around linkrev to filtered entry in 'filelog' revset This revset is used by 'hg log FILENAME'. This prevent bugs when used on a repository with hidden revisions. Instead of just discarding file revisions whose linkrevs point to filtered revisions, we put them aside and post-process them trying to find a non-filtered introduction. See inline documentation for details about how it works. This only fixes some of the problems. Once again, more will be needed when we can cannot rely on child revisions of a file to find linkrev-shadowned revisions. A test is added for 'hg log' catching such cases.
author Pierre-Yves David <pierre-yves.david@fb.com>
date Mon, 29 Dec 2014 17:23:16 -0800
parents c624fb2c4239
children 8ec03e0ef51a
comparison
equal deleted inserted replaced
23718:42908c3275c6 23719:34364a4b25eb
769 result, use ``file()``. 769 result, use ``file()``.
770 770
771 The pattern without explicit kind like ``glob:`` is expected to be 771 The pattern without explicit kind like ``glob:`` is expected to be
772 relative to the current directory and match against a file exactly 772 relative to the current directory and match against a file exactly
773 for efficiency. 773 for efficiency.
774
775 If some linkrev points to revisions filtered by the current repoview, we'll
776 work around it to return a non-filtered value.
774 """ 777 """
775 778
776 # i18n: "filelog" is a keyword 779 # i18n: "filelog" is a keyword
777 pat = getstring(x, _("filelog requires a pattern")) 780 pat = getstring(x, _("filelog requires a pattern"))
778 s = set() 781 s = set()
782 cl = repo.changelog
779 783
780 if not matchmod.patkind(pat): 784 if not matchmod.patkind(pat):
781 f = pathutil.canonpath(repo.root, repo.getcwd(), pat) 785 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
782 fl = repo.file(f) 786 files = [f]
783 for fr in fl:
784 s.add(fl.linkrev(fr))
785 else: 787 else:
786 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None]) 788 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
787 for f in repo[None]: 789 files = (f for f in repo[None] if m(f))
788 if m(f): 790
789 fl = repo.file(f) 791 for f in files:
790 for fr in fl: 792 backrevref = {} # final value for: changerev -> filerev
791 s.add(fl.linkrev(fr)) 793 lowestchild = {} # lowest known filerev child of a filerev
794 delayed = [] # filerev with filtered linkrev, for post-processing
795 fl = repo.file(f)
796 for fr in list(fl):
797 lkr = rev = fl.linkrev(fr)
798 if rev not in cl:
799 # changerev pointed in linkrev is filtered
800 # record it for post processing.
801 delayed.append((fr, rev))
802 continue
803 for p in fl.parentrevs(fr):
804 if 0 <= p and p not in lowestchild:
805 lowestchild[p] = fr
806 backrevref[fr] = rev
807 s.add(rev)
808
809 # Post-processing of all filerevs we skipped because they were
810 # filtered. If such filerevs have known and unfiltered children, this
811 # means they have an unfiltered appearance out there. We'll use linkrev
812 # adjustment to find one of these appearances. The lowest known child
813 # will be used as a starting point because it is the best upper-bound we
814 # have.
815 #
816 # This approach will fail when an unfiltered but linkrev-shadowed
817 # appearance exists in a head changeset without unfiltered filerev
818 # children anywhere.
819 while delayed:
820 # must be a descending iteration. To slowly fill lowest child
821 # information that is of potential use by the next item.
822 fr, rev = delayed.pop()
823 lkr = rev
824
825 child = lowestchild.get(fr)
826
827 if child is None:
828 # XXX content could be linkrev-shadowed in a head, but lets
829 # ignore this case for now.
830 continue
831 else:
832 # the lowest known child is a good upper bound
833 childcrev = backrevref[child]
834 # XXX this does not guarantee returning the lowest
835 # introduction of this revision, but this gives a
836 # result which is a good start and will fit in most
837 # cases. We probably need to fix the multiple
838 # introductions case properly (report each
839 # introduction, even for identical file revisions)
840 # once and for all at some point anyway.
841 for p in repo[childcrev][f].parents():
842 if p.filerev() == fr:
843 rev = p.rev()
844 break
845 if rev == lkr: # no shadowed entry found
846 # XXX This should never happen unless some manifest points
847 # to biggish file revisions (like a revision that uses a
848 # parent that never appears in the manifest ancestors)
849 continue
850
851 # Fill the data for the next iteration.
852 for p in fl.parentrevs(fr):
853 if 0 <= p and p not in lowestchild:
854 lowestchild[p] = fr
855 backrevref[fr] = rev
856 s.add(rev)
792 857
793 return subset & s 858 return subset & s
794 859
795 def first(repo, subset, x): 860 def first(repo, subset, x):
796 """``first(set, [n])`` 861 """``first(set, [n])``