diff --git a/mercurial/configitems.py b/mercurial/configitems.py --- a/mercurial/configitems.py +++ b/mercurial/configitems.py @@ -482,6 +482,9 @@ coreconfigitem('experimental', 'copytrace.sourcecommitlimit', default=100, ) +coreconfigitem('experimental', 'copies.read-from', + default="filelog-only", +) coreconfigitem('experimental', 'crecordtest', default=None, ) diff --git a/mercurial/copies.py b/mercurial/copies.py --- a/mercurial/copies.py +++ b/mercurial/copies.py @@ -166,6 +166,10 @@ # files might have to be traced back to the fctx parent of the last # one-side-only changeset, but not further back than that repo = a._repo + + if repo.ui.config('experimental', 'copies.read-from') == 'compatibility': + return _changesetforwardcopies(a, b, match) + debug = repo.ui.debugflag and repo.ui.configbool('devel', 'debug.copies') dbg = repo.ui.debug if debug: @@ -216,6 +220,76 @@ % (util.timer() - start)) return cm +def _changesetforwardcopies(a, b, match): + if a.rev() == node.nullrev: + return {} + + repo = a.repo() + children = {} + cl = repo.changelog + missingrevs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()]) + for r in missingrevs: + for p in cl.parentrevs(r): + if p == node.nullrev: + continue + if p not in children: + children[p] = [r] + else: + children[p].append(r) + + roots = set(children) - set(missingrevs) + # 'work' contains 3-tuples of a (revision number, parent number, copies). + # The parent number is only used for knowing which parent the copies dict + # came from. + work = [(r, 1, {}) for r in roots] + heapq.heapify(work) + while work: + r, i1, copies1 = heapq.heappop(work) + if work and work[0][0] == r: + # We are tracing copies from both parents + r, i2, copies2 = heapq.heappop(work) + copies = {} + ctx = repo[r] + p1man, p2man = ctx.p1().manifest(), ctx.p2().manifest() + allcopies = set(copies1) | set(copies2) + # TODO: perhaps this filtering should be done as long as ctx + # is merge, whether or not we're tracing from both parent. + for dst in allcopies: + if not match(dst): + continue + if dst not in copies2: + # Copied on p1 side: mark as copy from p1 side if it didn't + # already exist on p2 side + if dst not in p2man: + copies[dst] = copies1[dst] + elif dst not in copies1: + # Copied on p2 side: mark as copy from p2 side if it didn't + # already exist on p1 side + if dst not in p1man: + copies[dst] = copies2[dst] + else: + # Copied on both sides: mark as copy from p1 side + copies[dst] = copies1[dst] + else: + copies = copies1 + if r == b.rev(): + return copies + for c in children[r]: + childctx = repo[c] + if r == childctx.p1().rev(): + parent = 1 + childcopies = childctx.p1copies() + else: + assert r == childctx.p2().rev() + parent = 2 + childcopies = childctx.p2copies() + if not match.always(): + childcopies = {dst: src for dst, src in childcopies.items() + if match(dst)} + childcopies = _chain(a, childctx, copies, childcopies) + heapq.heappush(work, (c, parent, childcopies)) + assert False + def _forwardcopies(a, b, match=None): """find {dst@b: src@a} copy mapping where a is an ancestor of b""" diff --git a/tests/test-copies.t b/tests/test-copies.t --- a/tests/test-copies.t +++ b/tests/test-copies.t @@ -1,9 +1,17 @@ +#testcases filelog compatibility $ cat >> $HGRCPATH << EOF > [alias] > l = log -G -T '{rev} {desc}\n{files}\n' > EOF +#if compatibility + $ cat >> $HGRCPATH << EOF + > [experimental] + > copies.read-from = compatibility + > EOF +#endif + $ REPONUM=0 $ newrepo() { > cd $TESTTMP @@ -338,7 +346,7 @@ $ hg debugpathcopies 1 2 x -> z $ hg debugpathcopies 0 2 - x -> z + x -> z (filelog !) Copy file that exists on both sides of the merge, different content $ newrepo @@ -476,7 +484,8 @@ $ hg debugpathcopies 1 4 $ hg debugpathcopies 2 4 $ hg debugpathcopies 0 4 - x -> z + x -> z (filelog !) + y -> z (compatibility !) $ hg debugpathcopies 1 5 $ hg debugpathcopies 2 5 $ hg debugpathcopies 0 5