comparison mercurial/context.py @ 23703:aaa76612b3c0

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.
author Pierre-Yves David <pierre-yves.david@fb.com>
date Tue, 23 Dec 2014 16:14:39 -0800
parents c48924787eaa
children 28a302e9225d
comparison
equal deleted inserted replaced
23702:c48924787eaa 23703:aaa76612b3c0
20 # Phony node value to stand-in for new files in some uses of 20 # Phony node value to stand-in for new files in some uses of
21 # manifests. Manifests support 21-byte hashes for nodes which are 21 # manifests. Manifests support 21-byte hashes for nodes which are
22 # dirty in the working copy. 22 # dirty in the working copy.
23 _newnode = '!' * 21 23 _newnode = '!' * 21
24 24
25 def _adjustlinkrev(repo, path, filelog, fnode, srcrev): 25 def _adjustlinkrev(repo, path, filelog, fnode, srcrev, inclusive=False):
26 """return the first ancestor of <srcrev> introducting <fnode> 26 """return the first ancestor of <srcrev> introducting <fnode>
27 27
28 If the linkrev of the file revision does not point to an ancestor of 28 If the linkrev of the file revision does not point to an ancestor of
29 srcrev, we'll walk down the ancestors until we find one introducing this 29 srcrev, we'll walk down the ancestors until we find one introducing this
30 file revision. 30 file revision.
32 :repo: a localrepository object (used to access changelog and manifest) 32 :repo: a localrepository object (used to access changelog and manifest)
33 :path: the file path 33 :path: the file path
34 :fnode: the nodeid of the file revision 34 :fnode: the nodeid of the file revision
35 :filelog: the filelog of this path 35 :filelog: the filelog of this path
36 :srcrev: the changeset revision we search ancestors from 36 :srcrev: the changeset revision we search ancestors from
37 :inclusive: if true, the src revision will also be checked
37 """ 38 """
38 cl = repo.unfiltered().changelog 39 cl = repo.unfiltered().changelog
39 ma = repo.manifest 40 ma = repo.manifest
40 # fetch the linkrev 41 # fetch the linkrev
41 fr = filelog.rev(fnode) 42 fr = filelog.rev(fnode)
42 lkr = filelog.linkrev(fr) 43 lkr = filelog.linkrev(fr)
43 # check if this linkrev is an ancestor of srcrev 44 # check if this linkrev is an ancestor of srcrev
44 anc = cl.ancestors([srcrev], lkr) 45 anc = cl.ancestors([srcrev], lkr, inclusive=inclusive)
45 if lkr not in anc: 46 if lkr not in anc:
46 for a in anc: 47 for a in anc:
47 ac = cl.read(a) # get changeset data (we avoid object creation). 48 ac = cl.read(a) # get changeset data (we avoid object creation).
48 if path in ac[3]: # checking the 'files' field. 49 if path in ac[3]: # checking the 'files' field.
49 # The file has been touched, check if the content is similar 50 # The file has been touched, check if the content is similar
765 or self.size() == fctx.size()): 766 or self.size() == fctx.size()):
766 return self._filelog.cmp(self._filenode, fctx.data()) 767 return self._filelog.cmp(self._filenode, fctx.data())
767 768
768 return True 769 return True
769 770
771 def introrev(self):
772 """return the rev of the changeset which introduced this file revision
773
774 This method is different from linkrev because it take into account the
775 changeset the filectx was created from. It ensures the returned
776 revision is one of its ancestors. This prevents bugs from
777 'linkrev-shadowing' when a file revision is used by multiple
778 changesets.
779 """
780 lkr = self.linkrev()
781 attrs = vars(self)
782 noctx = not ('_changeid' in attrs or '_changectx' in attrs)
783 if noctx or self.rev() == lkr:
784 return self.linkrev()
785 return _adjustlinkrev(self._repo, self._path, self._filelog,
786 self._filenode, self.rev(), inclusive=True)
787
770 def parents(self): 788 def parents(self):
771 _path = self._path 789 _path = self._path
772 fl = self._filelog 790 fl = self._filelog
773 parents = self._filelog.parents(self._filenode) 791 parents = self._filelog.parents(self._filenode)
774 pl = [(_path, node, fl) for node in parents if node != nullid] 792 pl = [(_path, node, fl) for node in parents if node != nullid]