def _expandsubinclude(kindpats, root): '''Returns the list of subinclude matchers and the kindpats without the subincludes in it.''' relmatchers = [] other = [] for kind, pat, source in kindpats: if kind == 'subinclude': sourceroot = pathutil.dirname(util.normpath(source)) pat = util.pconvert(pat) path = pathutil.join(sourceroot, pat) newroot = pathutil.dirname(path) relmatcher = match(newroot, '', [], ['include:%s' % path]) prefix = pathutil.canonpath(root, root, newroot) if prefix: prefix += '/' relmatchers.append((prefix, relmatcher)) else: other.append((kind, pat, source)) return relmatchers, other
def mergecopies(repo, c1, c2, ca): """ Find moves and copies between context c1 and c2 that are relevant for merging. Returns four dicts: "copy", "movewithdir", "diverge", and "renamedelete". "copy" is a mapping from destination name -> source name, where source is in c1 and destination is in c2 or vice-versa. "movewithdir" is a mapping from source name -> destination name, where the file at source present in one context but not the other needs to be moved to destination by the merge process, because the other context moved the directory it is in. "diverge" is a mapping of source name -> list of destination names for divergent renames. "renamedelete" is a mapping of source name -> list of destination names for files deleted in c1 that were renamed in c2 or vice-versa. """ # avoid silly behavior for update from empty dir if not c1 or not c2 or c1 == c2: return {}, {}, {}, {} # avoid silly behavior for parent -> working dir if c2.node() is None and c1.node() == repo.dirstate.p1(): return repo.dirstate.copies(), {}, {}, {} limit = _findlimit(repo, c1.rev(), c2.rev()) if limit is None: # no common ancestor, no copies return {}, {}, {}, {} m1 = c1.manifest() m2 = c2.manifest() ma = ca.manifest() 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). 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 = {} diverge = {} repo.ui.debug(" searching for copies back to rev %d\n" % limit) addedinm1 = m1.filesnotin(ma) addedinm2 = m2.filesnotin(ma) u1, u2 = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2) for f in u1: ctx = setupctx(c1) 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 = {} renamedelete2 = set() diverge2 = set() for of, fl in diverge.items(): if len(fl) == 1 or of in c1 or of in c2: del diverge[of] # not actually divergent, or not a rename if of not in c1 and of not in c2: # renamed on one side, deleted on the other side, but filter # out files that have been renamed and then deleted renamedelete[of] = [f for f in fl if f in c1 or f in c2] renamedelete2.update(fl) # reverse map for below else: diverge2.update(fl) # reverse map for below bothnew = sorted(addedinm1 & addedinm2) if bothnew: repo.ui.debug(" unmatched files new in both:\n %s\n" % "\n ".join(bothnew)) bothdiverge, _copy, _fullcopy = {}, {}, {} for f in bothnew: ctx = setupctx(c1) 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]: copy[fl[0]] = of # not actually divergent, just matching renames if fullcopy and repo.ui.debugflag: repo.ui.debug(" all copies found (* = to merge, ! = divergent, " "% = renamed and deleted):\n") for f in sorted(fullcopy): note = "" if f in copy: note += "*" if f in diverge2: note += "!" if f in renamedelete2: note += "%" repo.ui.debug(" src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f, note)) del diverge2 if not fullcopy: return copy, movewithdir, diverge, renamedelete repo.ui.debug(" checking for directory renames\n") # generate a directory move map d1, d2 = c1.dirs(), c2.dirs() # Hack for adding '', which is not otherwise added, to d1 and d2 d1.addpath('/') d2.addpath('/') invalid = set() dirmove = {} # examine each file copy for a potential directory move, which is # when all the files in a directory are moved to a new directory for dst, src in fullcopy.iteritems(): dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst) if dsrc in invalid: # already seen to be uninteresting continue elif dsrc in d1 and ddst in d1: # directory wasn't entirely moved locally invalid.add(dsrc) elif dsrc in d2 and ddst in d2: # directory wasn't entirely moved remotely invalid.add(dsrc) elif dsrc in dirmove and dirmove[dsrc] != ddst: # files from the same directory moved to two different places invalid.add(dsrc) else: # looks good so far dirmove[dsrc + "/"] = ddst + "/" for i in invalid: if i in dirmove: del dirmove[i] del d1, d2, invalid if not dirmove: return copy, movewithdir, diverge, renamedelete for d in dirmove: repo.ui.debug(" discovered dir src: '%s' -> dst: '%s'\n" % (d, dirmove[d])) # check unaccounted nonoverlapping files against directory moves for f in u1 + u2: if f not in fullcopy: for d in dirmove: if f.startswith(d): # new file added in a directory that was moved, move it df = dirmove[d] + f[len(d):] if df not in copy: movewithdir[f] = df repo.ui.debug((" pending file src: '%s' -> " "dst: '%s'\n") % (f, df)) break return copy, movewithdir, diverge, renamedelete