Patchwork [1,of,2] linkrev: work around linkrev to filtered entry in 'filelog' revset

login
register
mail settings
Submitter Pierre-Yves David
Date Jan. 2, 2015, 6:50 a.m.
Message ID <ddf4f983fde248df05e4.1420181437@marginatus.alto.octopoid.net>
Download mbox | patch
Permalink /patch/7297/
State Accepted
Headers show

Comments

Pierre-Yves David - Jan. 2, 2015, 6:50 a.m.
# HG changeset patch
# User Pierre-Yves David <pierre-yves.david@fb.com>
# Date 1419902596 28800
#      Mon Dec 29 17:23:16 2014 -0800
# Node ID ddf4f983fde248df05e45229af5e173ba7680ca8
# Parent  3314664606e63e0ae263b71f5210e8153291efe8
linkrev: work around linkrev to filtered entry in 'filelog' revset

This revset is used by 'hg log FILENAME'. This prevent bugs when used on
repository with hidden revisions.

Instead of just discarding file revisions whose linkrev point to filtered
revision, 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 can
cannot rely on children revisions of a file to find linkrev-shadowned revisions.

The test are added for 'hg log' catching such cases.

Patch

diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -769,28 +769,92 @@  def filelog(repo, subset, x):
     result, use ``file()``.
 
     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 point to revisions filtered by the current repoview, we'll
+    work around 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-post processing of all filerev we skipped because they were
+        # filtered. If such filerev have known and unfiltered children, this
+        # mean they have an unfiltered appearance out there. We'll use linkrev
+        # adjustment to find one of this appearance. The lowest known child
+        # will be used as a starting point because it is the best upper-bound we
+        # have.
+        #
+        # This approach will fails when unfiltered but linkrev-shadowned
+        # appearance exists in 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 grantee to return the lowest introduction
+                # of this revision, but this give result which a good start and
+                # will fit in most case. We probably need to fix the multiple
+                # introductions case properly (report each introductions, even
+                # for identical file revision) 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 point
+                    # to biggish file revisions. (Like revision that use a
+                    # parent who that never appear 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
 
 def first(repo, subset, x):
     """``first(set, [n])``
diff --git a/tests/test-log.t b/tests/test-log.t
--- a/tests/test-log.t
+++ b/tests/test-log.t
@@ -1671,6 +1671,58 @@  hg log -f from the grafted changeset
   o  changeset:   0:ae0a3c9f9e95
      user:        test
      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 ..