Skip to content
Snippets Groups Projects
Commit 9372180b8c9b authored by Pierre-Yves David's avatar Pierre-Yves David
Browse files

mergecopies: reuse ancestry context when traversing file history (issue4537)

Merge copies is traversing file history in search for copies and renames.
Since 3.3 we are doing "linkrev adjustment" to ensure duplicated filelog entry
does not confuse the traversal. This "linkrev adjustment" involved ancestry
testing and walking in the changeset graph. If we do such walk in the changesets
graph for each file, we end up with a 'O(<changesets>x<files>)' complexity
that create massive issue. For examples, grafting a changeset in Mozilla's repo
moved from 6 seconds to more than 3 minutes.

There is a mechanism to reuse such ancestors computation between all files. But
it has to be manually set up in situation were it make sense to take such
shortcut. This changesets set this mechanism up and bring back the graph time
from 3 minutes to 8 seconds.

To do so, we need a bigger control on the way 'filectx' are instantiated during
each 'checkcopies' calls that 'mergecopies' is doing. We add a new 'setupctx'
that configure and return a 'filectx' factory. The function make sure the
ancestry context is properly created and the factory make sure it is properly
installed on returned 'filectx'.
parent 5a12ef618c03
No related branches found
No related tags found
No related merge requests found
......@@ -246,10 +246,15 @@
m2 = c2.manifest()
ma = ca.manifest()
def makectx(f, n):
if len(n) != 20: # in a working context?
if c1.rev() is None:
return c1.filectx(f)
return c2.filectx(f)
return repo.filectx(f, fileid=n)
def setupctx(ctx):
"""return a 'makectx' function suitable for checkcopies usage from ctx
We have to re-setup the function building 'filectx' for each
'checkcopies' to ensure the linkrev adjustement is properly setup for
each. Linkrev adjustment is important to avoid bug in rename
detection. Moreover, having a proper '_ancestrycontext' setup ensures
the performance impact of this adjustment is kept limited. Without it,
each file could do a full dag traversal making the time complexity of
the operation explode (see issue4537).
......@@ -255,5 +260,27 @@
ctx = util.lrucachefunc(makectx)
This function exists here mostly to limit the impact on stable. Feel
free to refactor on default.
"""
rev = ctx.rev()
ac = getattr(ctx, '_ancestrycontext', None)
if ac is None:
revs = [rev]
if rev is None:
revs = [p.rev() for p in ctx.parents()]
ac = ctx._repo.changelog.ancestors(revs, inclusive=True)
ctx._ancestrycontext = ac
def makectx(f, n):
if len(n) != 20: # in a working context?
if c1.rev() is None:
return c1.filectx(f)
return c2.filectx(f)
fctx = repo.filectx(f, fileid=n)
# setup only needed for filectx not create from a changectx
fctx._ancestrycontext = ac
fctx._descendantrev = rev
return fctx
return util.lrucachefunc(makectx)
copy = {}
movewithdir = {}
fullcopy = {}
......@@ -272,6 +299,7 @@
% "\n ".join(u2))
for f in u1:
ctx = setupctx(c1)
checkcopies(ctx, f, m1, m2, ca, limit, diverge, copy, fullcopy)
for f in u2:
......@@ -275,6 +303,7 @@
checkcopies(ctx, f, m1, m2, ca, limit, diverge, copy, fullcopy)
for f in u2:
ctx = setupctx(c2)
checkcopies(ctx, f, m2, m1, ca, limit, diverge, copy, fullcopy)
renamedelete = {}
......@@ -297,4 +326,5 @@
% "\n ".join(bothnew))
bothdiverge, _copy, _fullcopy = {}, {}, {}
for f in bothnew:
ctx = setupctx(c1)
checkcopies(ctx, f, m1, m2, ca, limit, bothdiverge, _copy, _fullcopy)
......@@ -300,4 +330,5 @@
checkcopies(ctx, f, m1, m2, ca, limit, bothdiverge, _copy, _fullcopy)
ctx = setupctx(c2)
checkcopies(ctx, f, m2, m1, ca, limit, bothdiverge, _copy, _fullcopy)
for of, fl in bothdiverge.items():
if len(fl) == 2 and fl[0] == fl[1]:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment