Ejemplo n.º 1
0
def dodiff(ui, repo, cmdline, pats, opts):
    """Do the actual diff:

    - copy to a temp structure if diffing 2 internal revisions
    - copy to a temp structure if diffing working revision with
      another one and more than 1 file is changed
    - just invoke the diff for a single file in the working dir
    """

    revs = opts.get("rev")
    change = opts.get("change")
    do3way = "$parent2" in cmdline

    if revs and change:
        msg = _("cannot specify --rev and --change at the same time")
        raise error.Abort(msg)
    elif change:
        node2 = scmutil.revsingle(repo, change, None).node()
        node1a, node1b = repo.changelog.parents(node2)
    else:
        node1a, node2 = scmutil.revpair(repo, revs)
        if not revs:
            node1b = repo.dirstate.p2()
        else:
            node1b = nullid

    # Disable 3-way merge if there is only one parent
    if do3way:
        if node1b == nullid:
            do3way = False

    matcher = scmutil.match(repo[node2], pats, opts)

    if opts.get("patch"):
        if node2 is None:
            raise error.Abort(_("--patch requires two revisions"))
    else:
        mod_a, add_a, rem_a = list(map(set, repo.status(node1a, node2, matcher)[:3]))
        if do3way:
            mod_b, add_b, rem_b = list(
                map(set, repo.status(node1b, node2, matcher)[:3])
            )
        else:
            mod_b, add_b, rem_b = set(), set(), set()
        modadd = mod_a | add_a | mod_b | add_b
        common = modadd | rem_a | rem_b
        if not common:
            return 0

    tmproot = tempfile.mkdtemp(prefix="extdiff.")
    try:
        if not opts.get("patch"):
            # Always make a copy of node1a (and node1b, if applicable)
            dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
            dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
            rev1a = "@%d" % repo[node1a].rev()
            if do3way:
                dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
                dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
                rev1b = "@%d" % repo[node1b].rev()
            else:
                dir1b = None
                rev1b = ""

            fnsandstat = []

            # If node2 in not the wc or there is >1 change, copy it
            dir2root = ""
            rev2 = ""
            if node2:
                dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
                rev2 = "@%d" % repo[node2].rev()
            elif len(common) > 1:
                # we only actually need to get the files to copy back to
                # the working dir in this case (because the other cases
                # are: diffing 2 revisions or single file -- in which case
                # the file is already directly passed to the diff tool).
                dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot)
            else:
                # This lets the diff tool open the changed file directly
                dir2 = ""
                dir2root = repo.root

            label1a = rev1a
            label1b = rev1b
            label2 = rev2

            # If only one change, diff the files instead of the directories
            # Handle bogus modifies correctly by checking if the files exist
            if len(common) == 1:
                common_file = util.localpath(common.pop())
                dir1a = os.path.join(tmproot, dir1a, common_file)
                label1a = common_file + rev1a
                if not os.path.isfile(dir1a):
                    dir1a = os.devnull
                if do3way:
                    dir1b = os.path.join(tmproot, dir1b, common_file)
                    label1b = common_file + rev1b
                    if not os.path.isfile(dir1b):
                        dir1b = os.devnull
                dir2 = os.path.join(dir2root, dir2, common_file)
                label2 = common_file + rev2
        else:
            template = "hg-%h.patch"
            cmdutil.export(
                repo,
                [repo[node1a].rev(), repo[node2].rev()],
                fntemplate=repo.localvfs.reljoin(tmproot, template),
                match=matcher,
            )
            label1a = cmdutil.makefilename(repo, template, node1a)
            label2 = cmdutil.makefilename(repo, template, node2)
            dir1a = repo.localvfs.reljoin(tmproot, label1a)
            dir2 = repo.localvfs.reljoin(tmproot, label2)
            dir1b = None
            label1b = None
            fnsandstat = []

        # Function to quote file/dir names in the argument string.
        # When not operating in 3-way mode, an empty string is
        # returned for parent2
        replace = {
            "parent": dir1a,
            "parent1": dir1a,
            "parent2": dir1b,
            "plabel1": label1a,
            "plabel2": label1b,
            "clabel": label2,
            "child": dir2,
            "root": repo.root,
        }

        def quote(match):
            pre = match.group(2)
            key = match.group(3)
            if not do3way and key == "parent2":
                return pre
            return pre + util.shellquote(replace[key])

        # Match parent2 first, so 'parent1?' will match both parent1 and parent
        regex = (
            r"""(['"]?)([^\s'"$]*)"""
            r"\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1"
        )
        if not do3way and not re.search(regex, cmdline):
            cmdline += " $parent1 $child"
        cmdline = re.sub(regex, quote, cmdline)

        ui.debug("running %r in %s\n" % (cmdline, tmproot))
        ui.system(cmdline, cwd=tmproot, blockedtag="extdiff")

        for copy_fn, working_fn, st in fnsandstat:
            cpstat = util.lstat(copy_fn)
            # Some tools copy the file and attributes, so mtime may not detect
            # all changes.  A size check will detect more cases, but not all.
            # The only certain way to detect every case is to diff all files,
            # which could be expensive.
            # copyfile() carries over the permission, so the mode check could
            # be in an 'elif' branch, but for the case where the file has
            # changed without affecting mtime or size.
            if (
                cpstat.st_mtime != st.st_mtime
                or cpstat.st_size != st.st_size
                or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)
            ):
                ui.debug(
                    "file changed while diffing. "
                    "Overwriting: %s (src: %s)\n" % (working_fn, copy_fn)
                )
                util.copyfile(copy_fn, working_fn)

        return 1
    finally:
        ui.note(_("cleaning up temp directory\n"))
        shutil.rmtree(tmproot)
Ejemplo n.º 2
0
def applytomirrors(repo, status, sourcepath, mirrors, action):
    """Applies the changes that are in the sourcepath to all the mirrors."""
    mirroredfiles = set()

    # Detect which mirror this file comes from
    sourcemirror = None
    for mirror in mirrors:
        if sourcepath.startswith(mirror):
            sourcemirror = mirror
            break
    if not sourcemirror:
        raise error.Abort(
            _("unable to detect source mirror of '%s'") % (sourcepath, ))

    relpath = sourcepath[len(sourcemirror):]

    # Apply the change to each mirror one by one
    allchanges = set(status.modified + status.removed + status.added)
    for mirror in mirrors:
        if mirror == sourcemirror:
            continue

        mirrorpath = mirror + relpath
        mirroredfiles.add(mirrorpath)
        if mirrorpath in allchanges:
            wctx = repo[None]
            if (sourcepath not in wctx and mirrorpath not in wctx
                    and sourcepath in status.removed
                    and mirrorpath in status.removed):
                if repo.ui.verbose:
                    repo.ui.status(
                        _("not mirroring remove of '%s' to '%s';"
                          " it is already removed\n") %
                        (sourcepath, mirrorpath))
                continue

            if wctx[sourcepath].data() == wctx[mirrorpath].data():
                if repo.ui.verbose:
                    repo.ui.status(
                        _("not mirroring '%s' to '%s'; it already "
                          "matches\n") % (sourcepath, mirrorpath))
                continue
            raise error.Abort(
                _("path '%s' needs to be mirrored to '%s', but "
                  "the target already has pending changes") %
                (sourcepath, mirrorpath))

        fullsource = repo.wjoin(sourcepath)
        fulltarget = repo.wjoin(mirrorpath)

        dirstate = repo.dirstate
        if action == "m" or action == "a":
            mirrorpathdir, unused = util.split(mirrorpath)
            util.makedirs(repo.wjoin(mirrorpathdir))

            util.copyfile(fullsource, fulltarget)
            if dirstate[mirrorpath] in "?r":
                dirstate.add(mirrorpath)

            if action == "a":
                # For adds, detect copy data as well
                copysource = dirstate.copied(sourcepath)
                if copysource and copysource.startswith(sourcemirror):
                    mirrorcopysource = mirror + copysource[len(sourcemirror):]
                    dirstate.copy(mirrorcopysource, mirrorpath)
                    repo.ui.status(
                        _("mirrored copy '%s -> %s' to '%s -> %s'\n") %
                        (copysource, sourcepath, mirrorcopysource, mirrorpath))
                else:
                    repo.ui.status(
                        _("mirrored adding '%s' to '%s'\n") %
                        (sourcepath, mirrorpath))
            else:
                repo.ui.status(
                    _("mirrored changes in '%s' to '%s'\n") %
                    (sourcepath, mirrorpath))
        elif action == "r":
            try:
                util.unlink(fulltarget)
            except OSError as e:
                if e.errno == errno.ENOENT:
                    repo.ui.status(
                        _("not mirroring remove of '%s' to '%s'; it "
                          "is already removed\n") % (sourcepath, mirrorpath))
                else:
                    raise
            else:
                dirstate.remove(mirrorpath)
                repo.ui.status(
                    _("mirrored remove of '%s' to '%s'\n") %
                    (sourcepath, mirrorpath))

    return mirroredfiles