# HG changeset patch # User Pierre-Yves David # Date 1419902596 28800 # Node ID 34364a4b25ebc62148fd012fe3ecb385872e5547 # Parent 42908c3275c63b7d2d6c871094c1e2c8f7ba31c8 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. diff -r 42908c3275c6 -r 34364a4b25eb mercurial/revset.py --- a/mercurial/revset.py Sun Dec 21 13:06:24 2014 -0800 +++ b/mercurial/revset.py Mon Dec 29 17:23:16 2014 -0800 @@ -771,24 +771,89 @@ The pattern without explicit kind like ``glob:`` is expected to be relative to the current directory and match against a file exactly for efficiency. + + If some linkrev points to revisions filtered by the current repoview, we'll + work around it to return a non-filtered value. """ # i18n: "filelog" is a keyword pat = getstring(x, _("filelog requires a pattern")) s = set() + cl = repo.changelog if not matchmod.patkind(pat): f = pathutil.canonpath(repo.root, repo.getcwd(), pat) - fl = repo.file(f) - for fr in fl: - s.add(fl.linkrev(fr)) + files = [f] else: m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None]) - for f in repo[None]: - if m(f): - fl = repo.file(f) - for fr in fl: - s.add(fl.linkrev(fr)) + files = (f for f in repo[None] if m(f)) + + for f in files: + backrevref = {} # final value for: changerev -> filerev + lowestchild = {} # lowest known filerev child of a filerev + delayed = [] # filerev with filtered linkrev, for post-processing + fl = repo.file(f) + for fr in list(fl): + lkr = rev = fl.linkrev(fr) + if rev not in cl: + # changerev pointed in linkrev is filtered + # record it for post processing. + delayed.append((fr, rev)) + continue + for p in fl.parentrevs(fr): + if 0 <= p and p not in lowestchild: + lowestchild[p] = fr + backrevref[fr] = rev + s.add(rev) + + # Post-processing of all filerevs we skipped because they were + # filtered. If such filerevs have known and unfiltered children, this + # means they have an unfiltered appearance out there. We'll use linkrev + # adjustment to find one of these appearances. The lowest known child + # will be used as a starting point because it is the best upper-bound we + # have. + # + # This approach will fail when an unfiltered but linkrev-shadowed + # appearance exists in a head changeset without unfiltered filerev + # children anywhere. + while delayed: + # must be a descending iteration. To slowly fill lowest child + # information that is of potential use by the next item. + fr, rev = delayed.pop() + lkr = rev + + child = lowestchild.get(fr) + + if child is None: + # XXX content could be linkrev-shadowed in a head, but lets + # ignore this case for now. + continue + else: + # the lowest known child is a good upper bound + childcrev = backrevref[child] + # XXX this does not guarantee returning the lowest + # introduction of this revision, but this gives a + # result which is a good start and will fit in most + # cases. We probably need to fix the multiple + # introductions case properly (report each + # introduction, even for identical file revisions) + # once and for all at some point anyway. + for p in repo[childcrev][f].parents(): + if p.filerev() == fr: + rev = p.rev() + break + if rev == lkr: # no shadowed entry found + # XXX This should never happen unless some manifest points + # to biggish file revisions (like a revision that uses a + # parent that never appears in the manifest ancestors) + continue + + # Fill the data for the next iteration. + for p in fl.parentrevs(fr): + if 0 <= p and p not in lowestchild: + lowestchild[p] = fr + backrevref[fr] = rev + s.add(rev) return subset & s diff -r 42908c3275c6 -r 34364a4b25eb tests/test-log.t --- a/tests/test-log.t Sun Dec 21 13:06:24 2014 -0800 +++ b/tests/test-log.t Mon Dec 29 17:23:16 2014 -0800 @@ -1673,4 +1673,56 @@ date: Thu Jan 01 00:00:00 1970 +0000 summary: content1 + +Test that we use the first non-hidden changeset in that case. + +(hide the changeset) + + $ hg log -T '{node}\n' -r 1 + 2294ae80ad8447bc78383182eeac50cb049df623 + $ hg debugobsolete 2294ae80ad8447bc78383182eeac50cb049df623 + $ hg log -G + o changeset: 4:50b9b36e9c5d + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: content3 + | + @ changeset: 3:15b2327059e5 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: content2 + | + o changeset: 2:2029acd1168c + | parent: 0:ae0a3c9f9e95 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: unrelated + | + o changeset: 0:ae0a3c9f9e95 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: content1 + + +Check that log on the file does not drop the file revision. + + $ hg log -G a + o changeset: 4:50b9b36e9c5d + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: content3 + | + @ changeset: 3:15b2327059e5 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: content2 + | + o changeset: 0:ae0a3c9f9e95 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: content1 + + $ cd ..