def dorecord()

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


def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts):
    from . import merge as mergemod

    if not ui.interactive():
        if cmdsuggest:
            msg = _("running non-interactively, use %s instead") % cmdsuggest
        else:
            msg = _("running non-interactively")
        raise error.Abort(msg)

    # make sure username is set before going interactive
    if not opts.get("user"):
        ui.username()  # raise exception, username not provided

    def recordfunc(ui, repo, message, match, opts):
        """This is generic record driver.

        Its job is to interactively filter local changes, and
        accordingly prepare working directory into a state in which the
        job can be delegated to a non-interactive commit command such as
        'commit' or 'qrefresh'.

        After the actual job is done by non-interactive command, the
        working directory is restored to its original state.

        In the end we'll record interesting changes, and everything else
        will be left in place, so the user can continue working.
        """

        checkunfinished(repo, commit=True)
        wctx = repo[None]
        merge = len(wctx.parents()) > 1
        if merge:
            raise error.Abort(
                _("cannot partially commit a merge " '(use "hg commit" instead)')
            )

        def fail(f, msg):
            raise error.Abort("%s: %s" % (f, msg))

        force = opts.get("force")
        if not force:
            match.bad = fail

        status = repo.status(match=match)
        if not force:
            repo.checkcommitpatterns(wctx, match, status, fail)
        diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
        diffopts.nodates = True
        diffopts.git = True
        diffopts.showfunc = True
        originaldiff = patch.diff(
            repo, repo[repo.dirstate.p1()], repo[None], changes=status, opts=diffopts
        )
        originalchunks = patch.parsepatch(originaldiff)

        # 1. filter patch, since we are intending to apply subset of it
        try:
            chunks, newopts = filterfn(ui, originalchunks)
        except error.PatchError as err:
            raise error.Abort(_("error parsing patch: %s") % err)
        opts.update(newopts)

        # We need to keep a backup of files that have been newly added and
        # modified during the recording process because there is a previous
        # version without the edit in the workdir
        newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
        contenders = set()
        for h in chunks:
            try:
                contenders.update(set(h.files()))
            except AttributeError:
                pass
        changed = status.modified + status.added + status.removed
        newfiles = [f for f in changed if f in contenders]
        if not newfiles:
            ui.status(_("no changes to record\n"))
            return 0

        modified = set(status.modified)

        # 2. backup changed files, so we can restore them in the end

        if backupall:
            tobackup = changed
        else:
            tobackup = [
                f for f in newfiles if f in modified or f in newlyaddedandmodifiedfiles
            ]
        copied = extractcopies(chunks)
        tobackup += sorted(copied.keys())  # backup "copyto" - delete by step 3a
        tobackup += sorted(copied.values())  # backup "copyfrom" - rewrite by step 3a
        backups = {}
        if tobackup:
            backupdir = repo.localvfs.join("record-backups")
            try:
                os.mkdir(backupdir)
            except OSError as err:
                if err.errno != errno.EEXIST:
                    raise
        try:
            # backup continues
            for f in tobackup:
                if not repo.wvfs.exists(f):
                    continue
                fd, tmpname = tempfile.mkstemp(dir=backupdir)
                os.close(fd)
                ui.debug("backup %r as %r\n" % (f, tmpname))
                util.copyfile(repo.wjoin(f), tmpname, copystat=True)
                backups[f] = tmpname

            fp = stringio()
            for c in chunks:
                if c.filename() in backups:
                    c.write(fp)
            dopatch = fp.tell()
            fp.seek(0)

            # 2.5 optionally review / modify patch in text editor
            if opts.get("review", False):
                patchtext = (
                    crecordmod.diffhelptext
                    + crecordmod.patchhelptext
                    + pycompat.decodeutf8(fp.read())
                )
                reviewedpatch = ui.edit(
                    patchtext, "", action="diff", repopath=repo.path
                )
                fp.truncate(0)
                fp.write(pycompat.encodeutf8(reviewedpatch))
                fp.seek(0)

            [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]

            # 3a. Prepare "copyfrom" -> "copyto" files. Write "copyfrom"
            # and remove "copyto".
            # This is used by patch._applydiff. If _applydiff reads directly
            # from repo["."], not repo.wvfs, then this could be unnecessary.
            for copyto, copyfrom in copied.items():
                content = repo["."][copyfrom].data()
                repo.wvfs.write(copyfrom, content)
                repo.wvfs.tryunlink(copyto)

            # 3b. apply filtered patch to clean repo  (clean)
            if backups:
                # Equivalent to hg.revert
                m = scmutil.matchfiles(repo, backups.keys())
                mergemod.update(repo, repo.dirstate.p1(), False, True, matcher=m)

            # 3c. (apply)
            if dopatch:
                try:
                    ui.debug("applying patch\n")
                    patch.internalpatch(ui, repo, fp, 1, eolmode=None)
                except error.PatchError as err:
                    raise error.Abort(str(err))
            del fp

            # 4. We prepared working directory according to filtered
            #    patch. Now is the time to delegate the job to
            #    commit/qrefresh or the like!

            # Make all of the pathnames absolute.
            newfiles = [repo.wjoin(nf) for nf in newfiles]
            return commitfunc(ui, repo, *newfiles, **opts)
        finally:
            # 5. finally restore backed-up files
            try:
                dirstate = repo.dirstate
                for realname, tmpname in pycompat.iteritems(backups):
                    ui.debug("restoring %r to %r\n" % (tmpname, realname))

                    if dirstate[realname] == "n":
                        # without normallookup, restoring timestamp
                        # may cause partially committed files
                        # to be treated as unmodified
                        dirstate.normallookup(realname)

                    # copystat=True here and above are a hack to trick any
                    # editors that have f open that we haven't modified them.
                    #
                    # Also note that this racy as an editor could notice the
                    # file's mtime before we've finished writing it.
                    util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
                    os.unlink(tmpname)
                if tobackup:
                    os.rmdir(backupdir)
            except OSError:
                pass

    def recordinwlock(ui, repo, message, match, opts):
        with repo.wlock():
            return recordfunc(ui, repo, message, match, opts)

    return commit(ui, repo, recordinwlock, pats, opts)