Patchwork [3,of,5] linkrev: introduce a 'introrev' method on filectx

login
register
mail settings
Submitter Pierre-Yves David
Date Dec. 30, 2014, 9:37 p.m.
Message ID <e64b95ee52529397007d.1419975430@marginatus.alto.octopoid.net>
Download mbox | patch
Permalink /patch/7264/
State Accepted
Headers show

Comments

Pierre-Yves David - Dec. 30, 2014, 9:37 p.m.
# HG changeset patch
# User Pierre-Yves David <pierre-yves.david@fb.com>
# Date 1419380079 28800
#      Tue Dec 23 16:14:39 2014 -0800
# Node ID e64b95ee52529397007da14ab71110a8d6a2f1c0
# Parent  d514ca98fbf1a73adc725d48dfe0a8a49b7fd82f
linkrev: introduce a 'introrev' method on filectx

The previous changeset properly fixed the ancestors computation, but we need to
ensure that the initial filectx is also using the right changeset.

When asking for log or annotation from a certain point, the first step is to
define the changeset that introduced the current file version. We cannot just
pick the "starting point" changesets" as it may just "use" the file revision,
unchanged.

For now, We were using 'linkrev' for this purpose, but this expose use to
unexpected branch jumping when the revision introducting the starting point
version is itself linkrev-shadowed.  So we need to take the topology into
account again. Therefore, we introduce a 'introrev' function, returning the
changeset who introduced the filechange in the current changesets.

This function will be used to fix linkrev related issue when bootstrapping 'hg
log --follow' and 'hg annotate'.

It reuses the '_adjustlinkrev' function, extending it to allow introspection of
the initial changeset too. In the previous usage of the '_adjustlinkrev' the
starting rev was always using a children file revisions, so it could be safely
ignored in the search. In this case, the starting point is using the revision
of the file we are looking, and may be the changeset we are looking for.

Patch

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -20,11 +20,11 @@  propertycache = util.propertycache
 # Phony node value to stand-in for new files in some uses of
 # manifests. Manifests support 21-byte hashes for nodes which are
 # dirty in the working copy.
 _newnode = '!' * 21
 
-def _adjustlinkrev(repo, path, filelog, fnode, srcrev):
+def _adjustlinkrev(repo, path, filelog, fnode, srcrev, inclusive=False):
     """return the first ancestor of <srcrev> introducting <fnode>
 
     If the linkrev of the file revision does not point to an ancestor of
     srcrev, we'll walk down the ancestors until we found one introducing this
     file revision.
@@ -32,18 +32,19 @@  def _adjustlinkrev(repo, path, filelog, 
     :repo: a localrepository object (used to access changelog and manifest)
     :path: the file path
     :fnode: the nodeid of the file revision
     :filelog: the filelog of this path
     :srcrev: the changeset revision we search ancestors from
+    :inclusive: if true, the src revision will also be checked
     """
     cl = repo.unfiltered().changelog
     ma = repo.manifest
     # fetch the linkrev
     fr = filelog.rev(fnode)
     lkr = filelog.linkrev(fr)
     # check if this linkrev is an ancestor of srcrev
-    anc = cl.ancestors([srcrev], lkr)
+    anc = cl.ancestors([srcrev], lkr, inclusive=inclusive)
     if lkr not in anc:
         for a in anc:
             ac = cl.read(a) # get changeset data (we avoid object creation).
             if path in ac[3]: # checking the 'files' field.
                 # The file have been touched, check if the content is similar
@@ -765,10 +766,27 @@  class basefilectx(object):
             or self.size() == fctx.size()):
             return self._filelog.cmp(self._filenode, fctx.data())
 
         return True
 
+    def introrev(self):
+        """return the revnum of the changeset who introduce this file revision
+
+        This methods is different from linkrev, because it take in account the
+        changeset the filectx was created from. It ensures the returned
+        revision is one of its ancestor. This prevent bug from
+        'linkrev-shadowing' when a  file revision is used by multiple
+        changesets.
+        """
+        lkr = self.linkrev()
+        attrs = vars(self)
+        noctx = not ('_changeid' in attrs or '_changectx' in attrs)
+        if noctx or self.rev() == lkr:
+            return self.linkrev()
+        return _adjustlinkrev(self._repo, self._path, self._filelog,
+                              self._filenode, self.rev(), inclusive=True)
+
     def parents(self):
         _path = self._path
         fl = self._filelog
         parents = self._filelog.parents(self._filenode)
         pl = [(_path, node, fl) for node in parents if node != nullid]