diff --git a/mercurial/context.py b/mercurial/context.py --- a/mercurial/context.py +++ b/mercurial/context.py @@ -22,6 +22,40 @@ propertycache = util.propertycache # dirty in the working copy. _newnode = '!' * 21 +def _adjustlinkrev(repo, path, filelog, fnode, srcrev): + """return the first ancestor of introducting + + If the linkrev of the file revision does not point to an ancestor of + srcrev, we'll walk down the ancestors until we find 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 has 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 uses a buggy file revision (not children of the one it + # replaces) we could. Such a 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, @@ -739,7 +773,7 @@ class basefilectx(object): 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 @@ -751,7 +785,19 @@ class basefilectx(object): # 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), ensure the created filectx is associated with a + # changeset 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] diff --git a/tests/test-annotate.t b/tests/test-annotate.t --- a/tests/test-annotate.t +++ b/tests/test-annotate.t @@ -451,3 +451,63 @@ Annotate with --ignore-blank-lines (simi 1: b b $ cd .. + +Annotate with linkrev pointing to another branch +------------------------------------------------ + +create history with a filerev whose linkrev points 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 .. diff --git a/tests/test-log.t b/tests/test-log.t --- a/tests/test-log.t +++ b/tests/test-log.t @@ -1564,3 +1564,96 @@ hg log -f dir across branches o a $ cd .. + +hg log -f with linkrev pointing to another branch +------------------------------------------------- + +create history with a filerev whose linkrev points 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 lists 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 .. diff --git a/tests/test-mv-cp-st-diff.t b/tests/test-mv-cp-st-diff.t --- a/tests/test-mv-cp-st-diff.t +++ b/tests/test-mv-cp-st-diff.t @@ -1605,3 +1605,65 @@ test for case where we didn't look suffi - +f $ cd .. + +Additional tricky linkrev case +------------------------------ + +If the first file revision after the diff base has a linkrev pointing to a +changeset on another branch with a revision lower that the diff base, we can +jump past 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 a 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 renames. 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 ..