# HG changeset patch # User Pierre-Yves David # Date 1419380079 28800 # Node ID aaa76612b3c064c6d0a9b6c10db2f4ff7753105a # Parent c48924787eaa8d43744fcffaf42727b217a1dd76 linkrev: introduce an '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. Currently, we were using 'linkrev' for this purpose, but this exposes us to unexpected branch-jumping when the revision introducing the starting point version is itself linkrev-shadowed. So we need to take the topology into account again. Therefore, we introduce an 'introrev' function, returning the changeset which introduced the file change in the current changeset. This function will be used to fix linkrev-related issues 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. diff -r c48924787eaa -r aaa76612b3c0 mercurial/context.py --- a/mercurial/context.py Tue Dec 23 15:30:38 2014 -0800 +++ b/mercurial/context.py Tue Dec 23 16:14:39 2014 -0800 @@ -22,7 +22,7 @@ # 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 introducting If the linkrev of the file revision does not point to an ancestor of @@ -34,6 +34,7 @@ :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 @@ -41,7 +42,7 @@ 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). @@ -767,6 +768,23 @@ return True + def introrev(self): + """return the rev of the changeset which introduced this file revision + + This method is different from linkrev because it take into account the + changeset the filectx was created from. It ensures the returned + revision is one of its ancestors. This prevents bugs 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