mercurial/copies.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43106 d783f945a701
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
   168 
   168 
   169 def _dirstatecopies(repo, match=None):
   169 def _dirstatecopies(repo, match=None):
   170     ds = repo.dirstate
   170     ds = repo.dirstate
   171     c = ds.copies().copy()
   171     c = ds.copies().copy()
   172     for k in list(c):
   172     for k in list(c):
   173         if ds[k] not in 'anm' or (match and not match(k)):
   173         if ds[k] not in b'anm' or (match and not match(k)):
   174             del c[k]
   174             del c[k]
   175     return c
   175     return c
   176 
   176 
   177 
   177 
   178 def _computeforwardmissing(a, b, match=None):
   178 def _computeforwardmissing(a, b, match=None):
   185     return mb.filesnotin(ma, match=match)
   185     return mb.filesnotin(ma, match=match)
   186 
   186 
   187 
   187 
   188 def usechangesetcentricalgo(repo):
   188 def usechangesetcentricalgo(repo):
   189     """Checks if we should use changeset-centric copy algorithms"""
   189     """Checks if we should use changeset-centric copy algorithms"""
   190     readfrom = repo.ui.config('experimental', 'copies.read-from')
   190     readfrom = repo.ui.config(b'experimental', b'copies.read-from')
   191     changesetsource = ('changeset-only', 'compatibility')
   191     changesetsource = (b'changeset-only', b'compatibility')
   192     return readfrom in changesetsource
   192     return readfrom in changesetsource
   193 
   193 
   194 
   194 
   195 def _committedforwardcopies(a, b, base, match):
   195 def _committedforwardcopies(a, b, base, match):
   196     """Like _forwardcopies(), but b.rev() cannot be None (working copy)"""
   196     """Like _forwardcopies(), but b.rev() cannot be None (working copy)"""
   199     repo = a._repo
   199     repo = a._repo
   200 
   200 
   201     if usechangesetcentricalgo(repo):
   201     if usechangesetcentricalgo(repo):
   202         return _changesetforwardcopies(a, b, match)
   202         return _changesetforwardcopies(a, b, match)
   203 
   203 
   204     debug = repo.ui.debugflag and repo.ui.configbool('devel', 'debug.copies')
   204     debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
   205     dbg = repo.ui.debug
   205     dbg = repo.ui.debug
   206     if debug:
   206     if debug:
   207         dbg('debug.copies:    looking into rename from %s to %s\n' % (a, b))
   207         dbg(b'debug.copies:    looking into rename from %s to %s\n' % (a, b))
   208     limit = _findlimit(repo, a, b)
   208     limit = _findlimit(repo, a, b)
   209     if debug:
   209     if debug:
   210         dbg('debug.copies:      search limit: %d\n' % limit)
   210         dbg(b'debug.copies:      search limit: %d\n' % limit)
   211     am = a.manifest()
   211     am = a.manifest()
   212     basemf = None if base is None else base.manifest()
   212     basemf = None if base is None else base.manifest()
   213 
   213 
   214     # find where new files came from
   214     # find where new files came from
   215     # we currently don't try to find where old files went, too expensive
   215     # we currently don't try to find where old files went, too expensive
   229     missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
   229     missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
   230 
   230 
   231     ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
   231     ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
   232 
   232 
   233     if debug:
   233     if debug:
   234         dbg('debug.copies:      missing files to search: %d\n' % len(missing))
   234         dbg(b'debug.copies:      missing files to search: %d\n' % len(missing))
   235 
   235 
   236     for f in sorted(missing):
   236     for f in sorted(missing):
   237         if debug:
   237         if debug:
   238             dbg('debug.copies:        tracing file: %s\n' % f)
   238             dbg(b'debug.copies:        tracing file: %s\n' % f)
   239         fctx = b[f]
   239         fctx = b[f]
   240         fctx._ancestrycontext = ancestrycontext
   240         fctx._ancestrycontext = ancestrycontext
   241 
   241 
   242         if debug:
   242         if debug:
   243             start = util.timer()
   243             start = util.timer()
   244         opath = _tracefile(fctx, am, basemf, limit)
   244         opath = _tracefile(fctx, am, basemf, limit)
   245         if opath:
   245         if opath:
   246             if debug:
   246             if debug:
   247                 dbg('debug.copies:          rename of: %s\n' % opath)
   247                 dbg(b'debug.copies:          rename of: %s\n' % opath)
   248             cm[f] = opath
   248             cm[f] = opath
   249         if debug:
   249         if debug:
   250             dbg(
   250             dbg(
   251                 'debug.copies:          time: %f seconds\n'
   251                 b'debug.copies:          time: %f seconds\n'
   252                 % (util.timer() - start)
   252                 % (util.timer() - start)
   253             )
   253             )
   254     return cm
   254     return cm
   255 
   255 
   256 
   256 
   340         copies = _committedforwardcopies(a, b, base, match)
   340         copies = _committedforwardcopies(a, b, base, match)
   341     return copies
   341     return copies
   342 
   342 
   343 
   343 
   344 def _backwardrenames(a, b, match):
   344 def _backwardrenames(a, b, match):
   345     if a._repo.ui.config('experimental', 'copytrace') == 'off':
   345     if a._repo.ui.config(b'experimental', b'copytrace') == b'off':
   346         return {}
   346         return {}
   347 
   347 
   348     # Even though we're not taking copies into account, 1:n rename situations
   348     # Even though we're not taking copies into account, 1:n rename situations
   349     # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
   349     # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
   350     # arbitrarily pick one of the renames.
   350     # arbitrarily pick one of the renames.
   364 
   364 
   365 
   365 
   366 def pathcopies(x, y, match=None):
   366 def pathcopies(x, y, match=None):
   367     """find {dst@y: src@x} copy mapping for directed compare"""
   367     """find {dst@y: src@x} copy mapping for directed compare"""
   368     repo = x._repo
   368     repo = x._repo
   369     debug = repo.ui.debugflag and repo.ui.configbool('devel', 'debug.copies')
   369     debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
   370     if debug:
   370     if debug:
   371         repo.ui.debug('debug.copies: searching copies from %s to %s\n' % (x, y))
   371         repo.ui.debug(
       
   372             b'debug.copies: searching copies from %s to %s\n' % (x, y)
       
   373         )
   372     if x == y or not x or not y:
   374     if x == y or not x or not y:
   373         return {}
   375         return {}
   374     a = y.ancestor(x)
   376     a = y.ancestor(x)
   375     if a == x:
   377     if a == x:
   376         if debug:
   378         if debug:
   377             repo.ui.debug('debug.copies: search mode: forward\n')
   379             repo.ui.debug(b'debug.copies: search mode: forward\n')
   378         if y.rev() is None and x == y.p1():
   380         if y.rev() is None and x == y.p1():
   379             # short-circuit to avoid issues with merge states
   381             # short-circuit to avoid issues with merge states
   380             return _dirstatecopies(repo, match)
   382             return _dirstatecopies(repo, match)
   381         copies = _forwardcopies(x, y, match=match)
   383         copies = _forwardcopies(x, y, match=match)
   382     elif a == y:
   384     elif a == y:
   383         if debug:
   385         if debug:
   384             repo.ui.debug('debug.copies: search mode: backward\n')
   386             repo.ui.debug(b'debug.copies: search mode: backward\n')
   385         copies = _backwardrenames(x, y, match=match)
   387         copies = _backwardrenames(x, y, match=match)
   386     else:
   388     else:
   387         if debug:
   389         if debug:
   388             repo.ui.debug('debug.copies: search mode: combined\n')
   390             repo.ui.debug(b'debug.copies: search mode: combined\n')
   389         base = None
   391         base = None
   390         if a.rev() != node.nullrev:
   392         if a.rev() != node.nullrev:
   391             base = x
   393             base = x
   392         copies = _chain(
   394         copies = _chain(
   393             _backwardrenames(x, a, match=match),
   395             _backwardrenames(x, a, match=match),
   451 
   453 
   452     # avoid silly behavior for parent -> working dir
   454     # avoid silly behavior for parent -> working dir
   453     if c2.node() is None and c1.node() == repo.dirstate.p1():
   455     if c2.node() is None and c1.node() == repo.dirstate.p1():
   454         return _dirstatecopies(repo, narrowmatch), {}, {}, {}, {}
   456         return _dirstatecopies(repo, narrowmatch), {}, {}, {}, {}
   455 
   457 
   456     copytracing = repo.ui.config('experimental', 'copytrace')
   458     copytracing = repo.ui.config(b'experimental', b'copytrace')
   457     if stringutil.parsebool(copytracing) is False:
   459     if stringutil.parsebool(copytracing) is False:
   458         # stringutil.parsebool() returns None when it is unable to parse the
   460         # stringutil.parsebool() returns None when it is unable to parse the
   459         # value, so we should rely on making sure copytracing is on such cases
   461         # value, so we should rely on making sure copytracing is on such cases
   460         return {}, {}, {}, {}, {}
   462         return {}, {}, {}, {}, {}
   461 
   463 
   464         return _fullcopytracing(repo, c1, c2, base)
   466         return _fullcopytracing(repo, c1, c2, base)
   465 
   467 
   466     # Copy trace disabling is explicitly below the node == p1 logic above
   468     # Copy trace disabling is explicitly below the node == p1 logic above
   467     # because the logic above is required for a simple copy to be kept across a
   469     # because the logic above is required for a simple copy to be kept across a
   468     # rebase.
   470     # rebase.
   469     if copytracing == 'heuristics':
   471     if copytracing == b'heuristics':
   470         # Do full copytracing if only non-public revisions are involved as
   472         # Do full copytracing if only non-public revisions are involved as
   471         # that will be fast enough and will also cover the copies which could
   473         # that will be fast enough and will also cover the copies which could
   472         # be missed by heuristics
   474         # be missed by heuristics
   473         if _isfullcopytraceable(repo, c1, base):
   475         if _isfullcopytraceable(repo, c1, base):
   474             return _fullcopytracing(repo, c1, c2, base)
   476             return _fullcopytracing(repo, c1, c2, base)
   488     """
   490     """
   489     if c1.rev() is None:
   491     if c1.rev() is None:
   490         c1 = c1.p1()
   492         c1 = c1.p1()
   491     if c1.mutable() and base.mutable():
   493     if c1.mutable() and base.mutable():
   492         sourcecommitlimit = repo.ui.configint(
   494         sourcecommitlimit = repo.ui.configint(
   493             'experimental', 'copytrace.sourcecommitlimit'
   495             b'experimental', b'copytrace.sourcecommitlimit'
   494         )
   496         )
   495         commits = len(repo.revs('%d::%d', base.rev(), c1.rev()))
   497         commits = len(repo.revs(b'%d::%d', base.rev(), c1.rev()))
   496         return commits < sourcecommitlimit
   498         return commits < sourcecommitlimit
   497     return False
   499     return False
   498 
   500 
   499 
   501 
   500 def _checksinglesidecopies(
   502 def _checksinglesidecopies(
   590     addedinm1 = m1.filesnotin(mb, repo.narrowmatch())
   592     addedinm1 = m1.filesnotin(mb, repo.narrowmatch())
   591     addedinm2 = m2.filesnotin(mb, repo.narrowmatch())
   593     addedinm2 = m2.filesnotin(mb, repo.narrowmatch())
   592     u1 = sorted(addedinm1 - addedinm2)
   594     u1 = sorted(addedinm1 - addedinm2)
   593     u2 = sorted(addedinm2 - addedinm1)
   595     u2 = sorted(addedinm2 - addedinm1)
   594 
   596 
   595     header = "  unmatched files in %s"
   597     header = b"  unmatched files in %s"
   596     if u1:
   598     if u1:
   597         repo.ui.debug("%s:\n   %s\n" % (header % 'local', "\n   ".join(u1)))
   599         repo.ui.debug(b"%s:\n   %s\n" % (header % b'local', b"\n   ".join(u1)))
   598     if u2:
   600     if u2:
   599         repo.ui.debug("%s:\n   %s\n" % (header % 'other', "\n   ".join(u2)))
   601         repo.ui.debug(b"%s:\n   %s\n" % (header % b'other', b"\n   ".join(u2)))
   600 
   602 
   601     fullcopy = copies1.copy()
   603     fullcopy = copies1.copy()
   602     fullcopy.update(copies2)
   604     fullcopy.update(copies2)
   603     if not fullcopy:
   605     if not fullcopy:
   604         return copy, {}, diverge, renamedelete, {}
   606         return copy, {}, diverge, renamedelete, {}
   605 
   607 
   606     if repo.ui.debugflag:
   608     if repo.ui.debugflag:
   607         repo.ui.debug(
   609         repo.ui.debug(
   608             "  all copies found (* = to merge, ! = divergent, "
   610             b"  all copies found (* = to merge, ! = divergent, "
   609             "% = renamed and deleted):\n"
   611             b"% = renamed and deleted):\n"
   610         )
   612         )
   611         for f in sorted(fullcopy):
   613         for f in sorted(fullcopy):
   612             note = ""
   614             note = b""
   613             if f in copy:
   615             if f in copy:
   614                 note += "*"
   616                 note += b"*"
   615             if f in divergeset:
   617             if f in divergeset:
   616                 note += "!"
   618                 note += b"!"
   617             if f in renamedeleteset:
   619             if f in renamedeleteset:
   618                 note += "%"
   620                 note += b"%"
   619             repo.ui.debug(
   621             repo.ui.debug(
   620                 "   src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f, note)
   622                 b"   src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f, note)
   621             )
   623             )
   622     del divergeset
   624     del divergeset
   623 
   625 
   624     repo.ui.debug("  checking for directory renames\n")
   626     repo.ui.debug(b"  checking for directory renames\n")
   625 
   627 
   626     # generate a directory move map
   628     # generate a directory move map
   627     d1, d2 = c1.dirs(), c2.dirs()
   629     d1, d2 = c1.dirs(), c2.dirs()
   628     invalid = set()
   630     invalid = set()
   629     dirmove = {}
   631     dirmove = {}
   654     del d1, d2, invalid
   656     del d1, d2, invalid
   655 
   657 
   656     if not dirmove:
   658     if not dirmove:
   657         return copy, {}, diverge, renamedelete, {}
   659         return copy, {}, diverge, renamedelete, {}
   658 
   660 
   659     dirmove = {k + "/": v + "/" for k, v in dirmove.iteritems()}
   661     dirmove = {k + b"/": v + b"/" for k, v in dirmove.iteritems()}
   660 
   662 
   661     for d in dirmove:
   663     for d in dirmove:
   662         repo.ui.debug(
   664         repo.ui.debug(
   663             "   discovered dir src: '%s' -> dst: '%s'\n" % (d, dirmove[d])
   665             b"   discovered dir src: '%s' -> dst: '%s'\n" % (d, dirmove[d])
   664         )
   666         )
   665 
   667 
   666     movewithdir = {}
   668     movewithdir = {}
   667     # check unaccounted nonoverlapping files against directory moves
   669     # check unaccounted nonoverlapping files against directory moves
   668     for f in u1 + u2:
   670     for f in u1 + u2:
   672                     # new file added in a directory that was moved, move it
   674                     # new file added in a directory that was moved, move it
   673                     df = dirmove[d] + f[len(d) :]
   675                     df = dirmove[d] + f[len(d) :]
   674                     if df not in copy:
   676                     if df not in copy:
   675                         movewithdir[f] = df
   677                         movewithdir[f] = df
   676                         repo.ui.debug(
   678                         repo.ui.debug(
   677                             ("   pending file src: '%s' -> " "dst: '%s'\n")
   679                             (b"   pending file src: '%s' -> " b"dst: '%s'\n")
   678                             % (f, df)
   680                             % (f, df)
   679                         )
   681                         )
   680                     break
   682                     break
   681 
   683 
   682     return copy, movewithdir, diverge, renamedelete, dirmove
   684     return copy, movewithdir, diverge, renamedelete, dirmove
   714 
   716 
   715     copies = {}
   717     copies = {}
   716 
   718 
   717     changedfiles = set()
   719     changedfiles = set()
   718     m1 = c1.manifest()
   720     m1 = c1.manifest()
   719     if not repo.revs('%d::%d', base.rev(), c2.rev()):
   721     if not repo.revs(b'%d::%d', base.rev(), c2.rev()):
   720         # If base is not in c2 branch, we switch to fullcopytracing
   722         # If base is not in c2 branch, we switch to fullcopytracing
   721         repo.ui.debug(
   723         repo.ui.debug(
   722             "switching to full copytracing as base is not "
   724             b"switching to full copytracing as base is not "
   723             "an ancestor of c2\n"
   725             b"an ancestor of c2\n"
   724         )
   726         )
   725         return _fullcopytracing(repo, c1, c2, base)
   727         return _fullcopytracing(repo, c1, c2, base)
   726 
   728 
   727     ctx = c2
   729     ctx = c2
   728     while ctx != base:
   730     while ctx != base:
   729         if len(ctx.parents()) == 2:
   731         if len(ctx.parents()) == 2:
   730             # To keep things simple let's not handle merges
   732             # To keep things simple let's not handle merges
   731             repo.ui.debug("switching to full copytracing because of merges\n")
   733             repo.ui.debug(b"switching to full copytracing because of merges\n")
   732             return _fullcopytracing(repo, c1, c2, base)
   734             return _fullcopytracing(repo, c1, c2, base)
   733         changedfiles.update(ctx.files())
   735         changedfiles.update(ctx.files())
   734         ctx = ctx.p1()
   736         ctx = ctx.p1()
   735 
   737 
   736     cp = _forwardcopies(base, c2)
   738     cp = _forwardcopies(base, c2)
   765             # c2.filectx(f) won't fail
   767             # c2.filectx(f) won't fail
   766             f2 = c2.filectx(f)
   768             f2 = c2.filectx(f)
   767             # we can have a lot of candidates which can slow down the heuristics
   769             # we can have a lot of candidates which can slow down the heuristics
   768             # config value to limit the number of candidates moves to check
   770             # config value to limit the number of candidates moves to check
   769             maxcandidates = repo.ui.configint(
   771             maxcandidates = repo.ui.configint(
   770                 'experimental', 'copytrace.movecandidateslimit'
   772                 b'experimental', b'copytrace.movecandidateslimit'
   771             )
   773             )
   772 
   774 
   773             if len(movecandidates) > maxcandidates:
   775             if len(movecandidates) > maxcandidates:
   774                 repo.ui.status(
   776                 repo.ui.status(
   775                     _(
   777                     _(
   776                         "skipping copytracing for '%s', more "
   778                         b"skipping copytracing for '%s', more "
   777                         "candidates than the limit: %d\n"
   779                         b"candidates than the limit: %d\n"
   778                     )
   780                     )
   779                     % (f, len(movecandidates))
   781                     % (f, len(movecandidates))
   780                 )
   782                 )
   781                 continue
   783                 continue
   782 
   784 
   831     filter copy records. Any copies that occur between fromrev and
   833     filter copy records. Any copies that occur between fromrev and
   832     skiprev will not be duplicated, even if they appear in the set of
   834     skiprev will not be duplicated, even if they appear in the set of
   833     copies between fromrev and rev.
   835     copies between fromrev and rev.
   834     """
   836     """
   835     exclude = {}
   837     exclude = {}
   836     ctraceconfig = repo.ui.config('experimental', 'copytrace')
   838     ctraceconfig = repo.ui.config(b'experimental', b'copytrace')
   837     bctrace = stringutil.parsebool(ctraceconfig)
   839     bctrace = stringutil.parsebool(ctraceconfig)
   838     if skiprev is not None and (
   840     if skiprev is not None and (
   839         ctraceconfig == 'heuristics' or bctrace or bctrace is None
   841         ctraceconfig == b'heuristics' or bctrace or bctrace is None
   840     ):
   842     ):
   841         # copytrace='off' skips this line, but not the entire function because
   843         # copytrace='off' skips this line, but not the entire function because
   842         # the line below is O(size of the repo) during a rebase, while the rest
   844         # the line below is O(size of the repo) during a rebase, while the rest
   843         # of the function is much faster (and is required for carrying copy
   845         # of the function is much faster (and is required for carrying copy
   844         # metadata across the rebase anyway).
   846         # metadata across the rebase anyway).