Mercurial > public > mercurial-scm > hg-stable
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] |