Exemplo n.º 1
0
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
Exemplo n.º 2
0
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
Exemplo n.º 3
0
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