@@ -20,10 +20,44 @@ 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):
+ """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.
+
+ :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
+ """
+ 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)
+ 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
+ # to the one we search for.
+ if fnode == ma.readdelta(ac[0]).get(path):
+ return a
+ # In Theory we should never get out of that loop without a result. But
+ # if manifest use a buggy file revision (not children of the one it
+ # replaces) we could. Such buggy situation will likely result is crash
+ # somewhere else at to some point.
+ return lkr
+
class basectx(object):
"""A basectx object represents the common logic for its children:
changectx: read-only context that is already present in the repo,
workingctx: a context that represents the working directory and can
be committed,
@@ -737,11 +771,11 @@ class basefilectx(object):
_path = self._path
fl = self._filelog
parents = self._filelog.parents(self._filenode)
pl = [(_path, node, fl) for node in parents if node != nullid]
- r = self._filelog.renamed(self._filenode)
+ r = fl.renamed(self._filenode)
if r:
# - In the simple rename case, both parent are nullid, pl is empty.
# - In case of merge, only one of the parent is null id and should
# be replaced with the rename information. This parent is -always-
# the first one.
@@ -749,11 +783,23 @@ class basefilectx(object):
# As null id have alway been filtered out in the previous list
# comprehension, inserting to 0 will always result in "replacing
# first nullid parent with rename information.
pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
- return [filectx(self._repo, p, fileid=n, filelog=l) for p, n, l in pl]
+ ret = []
+ for path, fnode, l in pl:
+ if '_changeid' in vars(self) or '_changectx' in vars(self):
+ # If self is associated with a changeset (probably explicitly
+ # fed), ensures the created filectx is associated to a
+ # changesets that is an ancestor of self.changectx.
+ rev = _adjustlinkrev(self._repo, path, l, fnode, self.rev())
+ fctx = filectx(self._repo, path, fileid=fnode, filelog=l,
+ changeid=rev)
+ else:
+ fctx = filectx(self._repo, path, fileid=fnode, filelog=l)
+ ret.append(fctx)
+ return ret
def p1(self):
return self.parents()[0]
def p2(self):
@@ -449,5 +449,65 @@ Annotate with --ignore-blank-lines (simi
0:
1:
1: b b
$ cd ..
+
+Annotate with linkrev pointing to other another branch
+------------------------------------------------------
+
+create history with a filerev whose linkrev point to another branch
+
+ $ hg init branchedlinkrev
+ $ cd branchedlinkrev
+ $ echo A > a
+ $ hg commit -Am 'contentA'
+ adding a
+ $ echo B >> a
+ $ hg commit -m 'contentB'
+ $ hg up --rev 'desc(contentA)'
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ echo unrelated > unrelated
+ $ hg commit -Am 'unrelated'
+ adding unrelated
+ created new head
+ $ hg graft -r 'desc(contentB)'
+ grafting 1:fd27c222e3e6 "contentB"
+ $ echo C >> a
+ $ hg commit -m 'contentC'
+ $ hg log -G
+ @ changeset: 4:072f1e8df249
+ | tag: tip
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: contentC
+ |
+ o changeset: 3:ff38df03cc4b
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: contentB
+ |
+ o changeset: 2:62aaf3f6fc06
+ | parent: 0:f0932f74827e
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: unrelated
+ |
+ | o changeset: 1:fd27c222e3e6
+ |/ user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: contentB
+ |
+ o changeset: 0:f0932f74827e
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: contentA
+
+
+Annotate should list ancestor of starting revision only
+
+ $ hg annotate a
+ 0: A
+ 3: B
+ 4: C
+
+ $ cd ..
@@ -1562,5 +1562,98 @@ hg log -f dir across branches
o b
|
o a
$ cd ..
+
+hg log -f with linkrev pointing to another branch
+-------------------------------------------------
+
+create history with a filerev whose linkrev point to another branch
+
+ $ hg init branchedlinkrev
+ $ cd branchedlinkrev
+ $ echo 1 > a
+ $ hg commit -Am 'content1'
+ adding a
+ $ echo 2 > a
+ $ hg commit -m 'content2'
+ $ hg up --rev 'desc(content1)'
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ echo unrelated > unrelated
+ $ hg commit -Am 'unrelated'
+ adding unrelated
+ created new head
+ $ hg graft -r 'desc(content2)'
+ grafting 1:2294ae80ad84 "content2"
+ $ echo 3 > a
+ $ hg commit -m 'content3'
+ $ hg log -G
+ @ changeset: 4:50b9b36e9c5d
+ | tag: tip
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: content3
+ |
+ o 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: 1:2294ae80ad84
+ |/ 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
+
+
+log -f on the file should list the graft result.
+
+ $ hg log -Gf a
+ @ changeset: 4:50b9b36e9c5d
+ | tag: tip
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: content3
+ |
+ o 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
+
+
+plain log list the original version
+(XXX we should probably list both)
+
+ $ hg log -G a
+ @ changeset: 4:50b9b36e9c5d
+ | tag: tip
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: content3
+ |
+ | o changeset: 1:2294ae80ad84
+ |/ 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 ..
@@ -1603,5 +1603,67 @@ test for case where we didn't look suffi
+++ b/g
@@ -1,1 +1,1 @@
-
+f
$ cd ..
+
+Additional tricky linkrev case
+------------------------------
+
+If the first file revision after the diff base have a link rev pointing to a
+changeset on another branch with a revision lower that the diff base, we can
+jump past that the copy detection limit and fail to detect the rename,
+
+ $ hg init diffstoplinkrev
+ $ cd diffstoplinkrev
+
+ $ touch f
+ $ hg ci -Aqm 'empty f'
+
+Make a simple change
+
+ $ echo change > f
+ $ hg ci -m 'change f'
+
+Make a second branch, we use named branch to create a simple commit
+that does not touch f.
+
+ $ hg up -qr 'desc(empty)'
+ $ hg branch -q dev
+ $ hg ci -Aqm dev
+
+Graft the initial change, as f was untouched, we reuse the same entry and the
+linkrev point to the older branch.
+
+ $ hg graft -q 'desc(change)'
+
+Make a rename because we want to track rename. It is also important that the
+faulty linkrev is not the "start" commit to ensure the linkrev will be used.
+
+ $ hg mv f renamed
+ $ hg ci -m renamed
+
+ $ hg log -G -T '{rev} {desc}'
+ @ 4 renamed
+ |
+ o 3 change f
+ |
+ o 2 dev
+ |
+ | o 1 change f
+ |/
+ o 0 empty f
+
+
+The copy tracking should still reach rev 2 (branch creation).
+accessing the parent of 4 (renamed) should not jump use to revision 1.
+
+ $ hg diff --git -r 'desc(dev)' -r .
+ diff --git a/f b/renamed
+ rename from f
+ rename to renamed
+ --- a/f
+ +++ b/renamed
+ @@ -0,0 +1,1 @@
+ +change
+
+ $ cd ..