Mercurial > public > mercurial-scm > hg
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])`` |