def filterpatch()

in eden/scm/edenscm/mercurial/patch.py [0:0]


def filterpatch(ui, headers, operation=None):
    """Interactively filter patch chunks into applied-only chunks"""
    messages = getmessages()

    if operation is None:
        operation = "record"

    def prompt(skipfile, skipall, query, chunk):
        """prompt query, and process base inputs

        - y/n for the rest of file
        - y/n for the rest
        - ? (help)
        - q (quit)

        Return True/False and possibly updated skipfile and skipall.
        """
        newpatches = None
        if skipall is not None:
            return skipall, skipfile, skipall, newpatches
        if skipfile is not None:
            return skipfile, skipfile, skipall, newpatches
        while True:
            resps = messages["help"][operation]
            r = ui.promptchoice("%s %s" % (query, resps))
            ui.write("\n")
            if r == 8:  # ?
                for c, t in ui.extractchoices(resps)[1]:
                    ui.write("%s - %s\n" % (c, encoding.lower(t)))
                continue
            elif r == 0:  # yes
                ret = True
            elif r == 1:  # no
                ret = False
            elif r == 2:  # Edit patch
                if chunk is None:
                    ui.write(_("cannot edit patch for whole file"))
                    ui.write("\n")
                    continue
                if chunk.header.binary():
                    ui.write(_("cannot edit patch for binary file"))
                    ui.write("\n")
                    continue
                # Patch comment based on the Git one (based on comment at end of
                # https://mercurial-scm.org/wiki/RecordExtension)
                phelp = "---" + _(
                    """
To remove '-' lines, make them ' ' lines (context).
To remove '+' lines, delete them.
Lines starting with # will be removed from the patch.

If the patch applies cleanly, the edited hunk will immediately be
added to the record list. If it does not apply cleanly, a rejects
file will be generated: you can use that when you try again. If
all lines of the hunk are removed, then the edit is aborted and
the hunk is left unchanged.
"""
                )
                phelp = pycompat.encodeutf8(phelp)
                (patchfd, patchfn) = tempfile.mkstemp(
                    prefix="hg-editor-", suffix=".diff", text=True
                )
                ncpatchfp = None
                try:
                    # Write the initial patch
                    f = util.fdopen(patchfd, "wb")
                    chunk.header.write(f)
                    chunk.write(f)
                    f.write(b"\n".join([b"# " + i for i in phelp.splitlines()]))
                    f.close()
                    # Start the editor and wait for it to complete
                    editor = ui.geteditor()
                    ret = ui.system(
                        '%s "%s"' % (editor, patchfn),
                        environ={"HGUSER": ui.username()},
                        blockedtag="filterpatch",
                    )
                    if ret != 0:
                        ui.warn(_("editor exited with exit code %d\n") % ret)
                        continue
                    # Remove comment lines
                    patchfp = open(patchfn, "rb")
                    ncpatchfp = stringio()
                    for line in util.iterfile(patchfp):
                        if not line.startswith(b"#"):
                            ncpatchfp.write(line)
                    patchfp.close()
                    ncpatchfp.seek(0)
                    newpatches = parsepatch(ncpatchfp)
                finally:
                    os.unlink(patchfn)
                    del ncpatchfp
                # Signal that the chunk shouldn't be applied as-is, but
                # provide the new patch to be used instead.
                ret = False
            elif r == 3:  # Skip
                ret = skipfile = False
            elif r == 4:  # file (Record remaining)
                ret = skipfile = True
            elif r == 5:  # done, skip remaining
                ret = skipall = False
            elif r == 6:  # all
                ret = skipall = True
            elif r == 7:  # quit
                raise error.Abort(_("user quit"))
            return ret, skipfile, skipall, newpatches

    seen = set()
    applied = {}  # 'filename' -> [] of chunks
    skipfile, skipall = None, None
    pos, total = 1, sum(len(h.hunks) for h in headers)

    class fd(object):
        @staticmethod
        def write(*args, **opts):
            ui.writebytes(*args, **opts)

    for h in headers:
        pos += len(h.hunks)
        skipfile = None
        fixoffset = 0
        hdr = b"".join(h.header)
        if hdr in seen:
            continue
        seen.add(hdr)
        if skipall is None:
            h.pretty(fd)
        msg = _("examine changes to %s?") % _(" and ").join(
            "'%s'" % f for f in h.files()
        )
        r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
        if not r:
            continue
        applied[h.filename()] = [h]
        if h.allhunks():
            applied[h.filename()] += h.hunks
            continue
        for i, chunk in enumerate(h.hunks):
            if skipfile is None and skipall is None:
                chunk.pretty(fd)
            if total == 1:
                msg = messages["single"][operation] % chunk.filename()
            else:
                idx = pos - len(h.hunks) + i
                msg = messages["multiple"][operation] % (idx, total, chunk.filename())
            r, skipfile, skipall, newpatches = prompt(skipfile, skipall, msg, chunk)
            if r:
                if fixoffset:
                    chunk = copy.copy(chunk)
                    chunk.toline += fixoffset
                applied[chunk.filename()].append(chunk)
            elif newpatches is not None:
                for newpatch in newpatches:
                    for newhunk in newpatch.hunks:
                        if fixoffset:
                            newhunk.toline += fixoffset
                        applied[newhunk.filename()].append(newhunk)
            else:
                fixoffset += chunk.removed - chunk.added
    return (
        sum(
            [h for h in pycompat.itervalues(applied) if h[0].special() or len(h) > 1],
            [],
        ),
        {},
    )