Ejemplo n.º 1
0
def _makerage(ui, repo, **opts):
    configoverrides = {
        # Make graphlog shorter.
        ("experimental", "graphshorten"):
        "1",
        # Force use of lines-square renderer, as the user's configuration may
        # not render properly in a text file.
        ("experimental", "graph.renderer"):
        "lines-square",
        # Reduce the amount of data used for debugnetwork speed tests to
        # increase the chance they complete within 20s.
        ("debugnetwork", "speed-test-download-size"):
        "4M",
        ("debugnetwork", "speed-test-upload-size"):
        "1M",
    }

    # Override the encoding to "UTF-8" to generate the rage in UTF-8.
    oldencoding = encoding.encoding
    oldencodingmode = encoding.encodingmode
    encoding.encoding = "UTF-8"
    encoding.encodingmode = "replace"

    def hgcmd(cmdname, *args, **additional_opts):
        cmd, opts = cmdutil.getcmdanddefaultopts(cmdname, commands.table)
        opts.update(additional_opts)

        _repo = repo
        if "_repo" in opts:
            _repo = opts["_repo"]
            del opts["_repo"]
        # If we failed to popbuffer for some reason, do not mess up with the
        # main `ui` object.
        newui = ui.copy()
        newui.pushbuffer(error=True, subproc=True)
        newui._colormode = None

        def remoteui(orig, src, opts):
            rui = orig(src, opts)
            rui._outputui = newui
            return rui

        try:
            with newui.configoverride(configoverrides,
                                      "rage"), extensions.wrappedfunction(
                                          hg, "remoteui", remoteui):
                if cmd.norepo:
                    cmd(newui, *args, **opts)
                else:
                    cmd(newui, _repo, *args, **opts)
        finally:
            return newui.popbuffer()

    basic = [
        ("date", lambda: time.ctime()),
        ("unixname", lambda: encoding.environ.get("LOGNAME")),
        ("hostname", lambda: socket.gethostname()),
        ("repo location", lambda: repo.root),
        ("cwd", lambda: pycompat.getcwd()),
        ("fstype", lambda: util.getfstype(repo.root)),
        ("active bookmark",
         lambda: bookmarks._readactive(repo, repo._bookmarks)),
        (
            "hg version",
            lambda: __import__("edenscm.mercurial.__version__").mercurial.
            __version__.version,
        ),
    ]

    def _edenfs_rage():
        ragecmd = "edenfsctl rage --stdout"
        if opts.get("preview"):
            return shcmd(ragecmd + " --dry-run")
        return shcmd(ragecmd)

    detailed = [
        (
            "disk space usage",
            lambda: shcmd(
                "wmic LogicalDisk Where DriveType=3 Get DeviceId,FileSystem,FreeSpace,Size"
                if pycompat.iswindows else "df -h",
                check=False,
            ),
        ),
        # smartlog as the user sees it
        ("hg sl", lambda: hgcmd("smartlog", template="{sl_debug}")),
        (
            "hg debugmetalog -t 'since 2d ago'",
            lambda: hgcmd("debugmetalog", time_range=["since 2d ago"]),
        ),
        (
            'first 20 lines of "hg status"',
            lambda: "\n".join(hgcmd("status").splitlines()[:20]),
        ),
        (
            "hg debugmutation -r 'draft() & date(-4)' -t 'since 4d ago'",
            lambda: hgcmd("debugmutation",
                          rev=["draft() & date(-4)"],
                          time_range=["since 4d ago"]),
        ),
        (
            "hg bookmarks --list-subscriptions",
            lambda: hgcmd("bookmarks", list_subscriptions=True),
        ),
        ("sigtrace", lambda: readsigtraces(repo)),
        (
            "hg blackbox",
            lambda: "\n".join(
                hgcmd("blackbox", pattern=BLACKBOX_PATTERN).splitlines()[-500:]
            ),
        ),
        ("hg summary", lambda: hgcmd("summary")),
        ("hg cloud status", lambda: hgcmd("cloud status")),
        ("hg debugprocesstree", lambda: hgcmd("debugprocesstree")),
        ("hg config (local)", lambda: "\n".join(localconfig(ui))),
        ("hg sparse", lambda: hgcmd("sparse")),
        ("hg debugchangelog", lambda: hgcmd("debugchangelog")),
        ("hg debugexpandpaths", lambda: hgcmd("debugexpandpaths")),
        ("hg debuginstall", lambda: hgcmd("debuginstall")),
        ("hg debugdetectissues", lambda: hgcmd("debugdetectissues")),
        ("usechg", usechginfo),
        (
            "uptime",
            lambda: shcmd("wmic path Win32_OperatingSystem get LastBootUpTime"
                          if pycompat.iswindows else "uptime"),
        ),
        ("rpm info", (partial(rpminfo, ui))),
        ("klist", lambda: shcmd("klist", check=False)),
        ("ifconfig", lambda: shcmd("ipconfig"
                                   if pycompat.iswindows else "ifconfig")),
        (
            "airport",
            lambda: shcmd(
                "/System/Library/PrivateFrameworks/Apple80211." +
                "framework/Versions/Current/Resources/airport " + "--getinfo",
                check=False,
            ),
        ),
        ("hg debugnetwork", lambda: hgcmd("debugnetwork")),
        ("infinitepush backup state",
         lambda: readinfinitepushbackupstate(repo)),
        ("commit cloud workspace sync state",
         lambda: readcommitcloudstate(repo)),
        (
            "infinitepush / commitcloud backup logs",
            lambda: infinitepushbackuplogs(ui, repo),
        ),
        ("scm daemon logs", lambda: scmdaemonlog(ui, repo)),
        ("debugstatus", lambda: hgcmd("debugstatus")),
        ("debugtree", lambda: hgcmd("debugtree")),
        ("hg config (all)", lambda: "\n".join(allconfig(ui))),
        ("edenfs rage", _edenfs_rage),
        (
            "environment variables",
            lambda: "\n".join(
                sorted([
                    "{}={}".format(k, v) for k, v in encoding.environ.items()
                ])),
        ),
        ("ssh config",
         lambda: shcmd("ssh -G hg.vip.facebook.com", check=False)),
        ("debuglocks", lambda: hgcmd("debuglocks")),
        ("x2pagentd info", lambda: checkproxyagentstate(ui)),
    ]

    msg = ""

    if util.safehasattr(repo, "name"):
        # Add the contents of both local and shared pack directories.
        packlocs = {
            "local":
            lambda category: shallowutil.getlocalpackpath(
                repo.svfs.vfs.base, category),
            "shared":
            lambda category: shallowutil.getcachepackpath(repo, category),
        }

        for loc, getpath in pycompat.iteritems(packlocs):
            for category in constants.ALL_CATEGORIES:
                path = getpath(category)
                detailed.append((
                    "%s packs (%s)" % (loc, constants.getunits(category)),
                    lambda path=path: "%s:\n%s" % (
                        path,
                        shcmd("dir /o-s %s" % os.path.normpath(path)
                              if pycompat.iswindows else "ls -lhS %s" % path),
                    ),
                ))

    footnotes = []
    timeout = opts.get("timeout") or 20

    def _failsafe(gen, timeout=timeout):
        class TimedOut(RuntimeError):
            pass

        def target(result, gen):
            try:
                result.append(gen())
            except TimedOut:
                return
            except Exception as ex:
                index = len(footnotes) + 1
                footnotes.append("[%d]: %s\n%s\n\n" %
                                 (index, str(ex), traceback.format_exc()))
                result.append("(Failed. See footnote [%d])" % index)

        result = []
        thread = threading.Thread(target=target, args=(result, gen))
        thread.daemon = True
        thread.start()
        thread.join(timeout)
        if result:
            value = result[0]
            return value
        else:
            if thread.is_alive():
                # Attempt to stop the thread, since hg is not thread safe.
                # There is no pure Python API to interrupt a thread.
                # But CPython C API can do that.
                ctypes.pythonapi.PyThreadState_SetAsyncExc(
                    ctypes.c_long(thread.ident), ctypes.py_object(TimedOut))
            return (
                "(Did not complete in %s seconds, rerun with a larger --timeout to collect this)"
                % timeout)

    msg = []
    profile = []
    allstart = time.time()
    for name, gen in basic:
        msg.append("%s: %s\n\n" % (name, _failsafe(gen)))
    profile.append((time.time() - allstart, "basic info", None))
    for name, gen in detailed:
        start = time.time()
        with progress.spinner(ui, "collecting %r" % name):
            value = _failsafe(gen)
        finish = time.time()
        msg.append("%s: (%.2f s)\n---------------------------\n%s\n\n" %
                   (name, finish - start, value))
        profile.append((finish - start, name, value.count("\n")))
    allfinish = time.time()
    profile.append((allfinish - allstart, "total time", None))

    msg.append("hg rage profile:\n")
    width = max([len(name) for _t, name, _l in profile])
    for timetaken, name, lines in reversed(sorted(profile)):
        m = "  %-*s  %8.2f s" % (width + 1, name + ":", timetaken)
        if lines is not None:
            msg.append("%s for %4d lines\n" % (m, lines))
        else:
            msg.append("%s\n" % m)
    msg.append("\n")

    msg.extend(footnotes)
    msg = "".join(msg)

    encoding.encoding = oldencoding
    encoding.encodingmode = oldencodingmode
    return msg
Ejemplo n.º 2
0
Archivo: rage.py Proyecto: jsoref/eden
def _makerage(ui, repo, **opts):
    # Make graphlog shorter.
    configoverrides = {("experimental", "graphshorten"): "1"}

    def hgcmd(cmdname, *args, **additional_opts):
        cmd, opts = cmdutil.getcmdanddefaultopts(cmdname, commands.table)
        opts.update(additional_opts)

        _repo = repo
        if "_repo" in opts:
            _repo = opts["_repo"]
            del opts["_repo"]
        # If we failed to popbuffer for some reason, do not mess up with the
        # main `ui` object.
        newui = ui.copy()
        newui.pushbuffer(error=True)
        try:
            with ui.configoverride(configoverrides, "rage"):
                if cmd.norepo:
                    cmd(newui, *args, **opts)
                else:
                    cmd(newui, _repo, *args, **opts)
        finally:
            return newui.popbuffer()

    basic = [
        ("date", lambda: time.ctime()),
        ("unixname", lambda: encoding.environ.get("LOGNAME")),
        ("hostname", lambda: socket.gethostname()),
        ("repo location", lambda: repo.root),
        ("cwd", lambda: pycompat.getcwd()),
        ("fstype", lambda: util.getfstype(repo.root)),
        ("active bookmark", lambda: bookmarks._readactive(repo, repo._bookmarks)),
        (
            "hg version",
            lambda: __import__(
                "edenscm.mercurial.__version__"
            ).mercurial.__version__.version,
        ),
        ("obsstore size", lambda: str(repo.svfs.stat("obsstore").st_size)),
    ]

    oldcolormode = ui._colormode
    ui._colormode = None

    detailed = [
        ("df -h", lambda: shcmd("df -h", check=False)),
        # smartlog as the user sees it
        ("hg sl", lambda: hgcmd("smartlog", template="{sl_debug}")),
        # unfiltered smartlog for recent hidden changesets, including full
        # node identity
        (
            "hg sl --master='interestingmaster()' -r 'predecessors(draft())'",
            lambda: hgcmd(
                "smartlog",
                master="interestingmaster()",
                rev=["predecessors(draft())"],
                _repo=repo.unfiltered(),
                template='{sub("\\n", " ", "{node} {sl_debug}")}',
            ),
        ),
        (
            'first 20 lines of "hg status"',
            lambda: "\n".join(hgcmd("status").splitlines()[:20]),
        ),
        (
            "hg blackbox",
            lambda: "\n".join(
                hgcmd("blackbox", pattern=BLACKBOX_PATTERN).splitlines()[-500:]
            ),
        ),
        ("hg summary", lambda: hgcmd("summary")),
        ("hg cloud status", lambda: hgcmd("cloud status")),
        ("hg debugprocesstree", lambda: hgcmd("debugprocesstree")),
        ("hg config (local)", lambda: "\n".join(localconfig(ui))),
        ("hg sparse show", lambda: hgcmd("sparse show")),
        ("hg debuginstall", lambda: hgcmd("debuginstall")),
        ("usechg", (usechginfo)),
        (
            "uptime",
            lambda: shcmd(
                "wmic path Win32_OperatingSystem get LastBootUpTime"
                if pycompat.iswindows
                else "uptime"
            ),
        ),
        ("rpm info", (partial(rpminfo, ui))),
        ("klist", lambda: shcmd("klist", check=False)),
        ("ifconfig", lambda: shcmd("ipconfig" if pycompat.iswindows else "ifconfig")),
        (
            "airport",
            lambda: shcmd(
                "/System/Library/PrivateFrameworks/Apple80211."
                + "framework/Versions/Current/Resources/airport "
                + "--getinfo",
                check=False,
            ),
        ),
        (
            'last 100 lines of "hg debugobsolete"',
            lambda: "\n".join(hgcmd("debugobsolete").splitlines()[-100:]),
        ),
        ("infinitepush backup state", lambda: readinfinitepushbackupstate(repo)),
        ("commit cloud workspace sync state", lambda: readcommitcloudstate(repo)),
        (
            "infinitepush / commitcloud backup logs",
            lambda: infinitepushbackuplogs(ui, repo),
        ),
        ("scm daemon logs", lambda: scmdaemonlog(ui, repo)),
        ("debugstatus", lambda: hgcmd("debugstatus")),
        ("debugtree", lambda: hgcmd("debugtree")),
        ("hg config (overrides)", lambda: "\n".join(overriddenconfig(ui))),
        ("edenfs rage", lambda: shcmd("edenfsctl rage --stdout")),
        (
            "environment variables",
            lambda: "\n".join(
                sorted(["{}={}".format(k, v) for k, v in encoding.environ.items()])
            ),
        ),
        ("ssh config", lambda: shcmd("ssh -G hg.vip.facebook.com", check=False)),
    ]

    msg = ""

    if util.safehasattr(repo, "name"):
        # Add the contents of both local and shared pack directories.
        packlocs = {
            "local": lambda category: shallowutil.getlocalpackpath(
                repo.svfs.vfs.base, category
            ),
            "shared": lambda category: shallowutil.getcachepackpath(repo, category),
        }

        for loc, getpath in packlocs.iteritems():
            for category in constants.ALL_CATEGORIES:
                path = getpath(category)
                detailed.append(
                    (
                        "%s packs (%s)" % (loc, constants.getunits(category)),
                        lambda path=path: "%s:\n%s"
                        % (
                            path,
                            shcmd(
                                "dir /o-s %s" % os.path.normpath(path)
                                if pycompat.iswindows
                                else "ls -lhS %s" % path
                            ),
                        ),
                    )
                )

    footnotes = []
    timeout = opts.get("timeout") or 20

    def _failsafe(gen, timeout=timeout):
        class TimedOut(RuntimeError):
            pass

        def target(result, gen):
            try:
                result.append(gen())
            except TimedOut:
                return
            except Exception as ex:
                index = len(footnotes) + 1
                footnotes.append(
                    "[%d]: %s\n%s\n\n" % (index, str(ex), traceback.format_exc())
                )
                result.append("(Failed. See footnote [%d])" % index)

        result = []
        thread = threading.Thread(target=target, args=(result, gen))
        thread.daemon = True
        thread.start()
        thread.join(timeout)
        if result:
            value = result[0]
            return value
        else:
            if thread.is_alive():
                # Attempt to stop the thread, since hg is not thread safe.
                # There is no pure Python API to interrupt a thread.
                # But CPython C API can do that.
                ctypes.pythonapi.PyThreadState_SetAsyncExc(
                    ctypes.c_long(thread.ident), ctypes.py_object(TimedOut)
                )
            return (
                "(Did not complete in %s seconds, rerun with a larger --timeout to collect this)"
                % timeout
            )

    msg = []
    profile = []
    allstart = time.time()
    for name, gen in basic:
        msg.append("%s: %s\n\n" % (name, _failsafe(gen)))
    profile.append((time.time() - allstart, "basic info", None))
    for name, gen in detailed:
        start = time.time()
        with progress.spinner(ui, "collecting %r" % name):
            value = _failsafe(gen)
        finish = time.time()
        msg.append(
            "%s: (%.2f s)\n---------------------------\n%s\n\n"
            % (name, finish - start, value)
        )
        profile.append((finish - start, name, value.count("\n")))
    allfinish = time.time()
    profile.append((allfinish - allstart, "total time", None))

    msg.append("hg rage profile:\n")
    width = max([len(name) for _t, name, _l in profile])
    for timetaken, name, lines in reversed(sorted(profile)):
        m = "  %-*s  %8.2f s" % (width + 1, name + ":", timetaken)
        if lines is not None:
            msg.append("%s for %4d lines\n" % (m, lines))
        else:
            msg.append("%s\n" % m)
    msg.append("\n")

    msg.extend(footnotes)
    msg = "".join(msg)

    ui._colormode = oldcolormode
    return msg
Ejemplo n.º 3
0
def _makerage(ui, repo, **opts):
    configoverrides = {
        # Make graphlog shorter.
        ("experimental", "graphshorten"): "1",
        # Force use of lines-square renderer, as the user's configuration may
        # not render properly in a text file.
        ("experimental", "graph.renderer"): "lines-square",
        # Reduce the amount of data used for debugnetwork speed tests to
        # increase the chance they complete within 20s.
        ("debugnetwork", "speed-test-download-size"): "4M",
        ("debugnetwork", "speed-test-upload-size"): "1M",
    }

    # Override the encoding to "UTF-8" to generate the rage in UTF-8.
    oldencoding = encoding.encoding
    oldencodingmode = encoding.encodingmode
    encoding.encoding = "UTF-8"
    encoding.encodingmode = "replace"

    def hgcmd(cmdname, *args, **additional_opts):
        cmdargs = ["hg", *cmdname.split(), *args]
        for flagname, flagvalue in additional_opts.items():
            flagname = flagname.replace("_", "-")
            if isinstance(flagvalue, list):
                cmdargs += [f"--{flagname}={v}" for v in flagvalue]
            else:
                cmdargs += [f"--{flagname}={flagvalue}"]
        fin = util.stringio()
        fout = ferr = util.stringio()
        status = bindings.commands.run(cmdargs, fin, fout, ferr)

        output = fout.getvalue().decode()
        if status != 0:
            output += f"[{status}]\n"
        return output

    basic = [
        ("date", lambda: time.ctime()),
        ("unixname", lambda: encoding.environ.get("LOGNAME")),
        ("hostname", lambda: socket.gethostname()),
        ("repo location", lambda: repo.root),
        ("cwd", lambda: pycompat.getcwd()),
        ("fstype", lambda: util.getfstype(repo.root)),
        ("active bookmark", lambda: bookmarks._readactive(repo, repo._bookmarks)),
        (
            "hg version",
            lambda: __import__(
                "edenscm.mercurial.__version__"
            ).mercurial.__version__.version,
        ),
    ]

    def _edenfs_rage():
        ragecmd = "edenfsctl rage --stdout"
        if opts.get("preview"):
            return shcmd(ragecmd + " --dry-run")
        return shcmd(ragecmd)

    detailed = [
        (
            "disk space usage",
            lambda: shcmd(
                "wmic LogicalDisk Where DriveType=3 Get DeviceId,FileSystem,FreeSpace,Size"
                if pycompat.iswindows
                else "df -h",
                check=False,
            ),
        ),
        # smartlog as the user sees it
        ("hg sl", lambda: hgcmd("smartlog", template="{sl_debug}")),
        (
            "hg debugmetalog -t 'since 2d ago'",
            lambda: hgcmd("debugmetalog", time_range=["since 2d ago"]),
        ),
        (
            'first 20 lines of "hg status"',
            lambda: "\n".join(hgcmd("status").splitlines()[:20]),
        ),
        (
            "hg debugmutation -r 'draft() & date(-4)' -t 'since 4d ago'",
            lambda: hgcmd(
                "debugmutation", rev=["draft() & date(-4)"], time_range=["since 4d ago"]
            ),
        ),
        (
            "hg bookmarks --list-subscriptions",
            lambda: hgcmd("bookmarks", list_subscriptions=True),
        ),
        ("sigtrace", lambda: readsigtraces(repo)),
        (
            "hg blackbox",
            lambda: "\n".join(
                hgcmd("blackbox", pattern=BLACKBOX_PATTERN).splitlines()[-500:]
            ),
        ),
        ("hg summary", lambda: hgcmd("summary")),
        ("hg cloud status", lambda: hgcmd("cloud status")),
        ("hg debugprocesstree", lambda: hgcmd("debugprocesstree")),
        ("hg config (local)", lambda: "\n".join(localconfig(ui))),
        ("hg sparse", lambda: hgcmd("sparse")),
        ("hg debugchangelog", lambda: hgcmd("debugchangelog")),
        ("hg debugexpandpaths", lambda: hgcmd("debugexpandpaths")),
        ("hg debuginstall", lambda: hgcmd("debuginstall")),
        ("hg debugdetectissues", lambda: hgcmd("debugdetectissues")),
        ("usechg", usechginfo),
        (
            "uptime",
            lambda: shcmd(
                "wmic path Win32_OperatingSystem get LastBootUpTime"
                if pycompat.iswindows
                else "uptime"
            ),
        ),
        ("rpm info", (partial(rpminfo, ui))),
        ("klist", lambda: shcmd("klist", check=False)),
        ("ifconfig", lambda: shcmd("ipconfig" if pycompat.iswindows else "ifconfig")),
        (
            "airport",
            lambda: shcmd(
                "/System/Library/PrivateFrameworks/Apple80211."
                + "framework/Versions/Current/Resources/airport "
                + "--getinfo",
                check=False,
            ),
        ),
        ("hg debugnetwork", lambda: hgcmd("debugnetwork")),
        ("hg debugnetworkdoctor", lambda: hgcmd("debugnetworkdoctor")),
        ("infinitepush backup state", lambda: readinfinitepushbackupstate(repo)),
        ("commit cloud workspace sync state", lambda: readcommitcloudstate(repo)),
        (
            "infinitepush / commitcloud backup logs",
            lambda: infinitepushbackuplogs(ui, repo),
        ),
        ("scm daemon logs", lambda: scmdaemonlog(ui, repo)),
        ("debugstatus", lambda: hgcmd("debugstatus")),
        ("debugtree", lambda: hgcmd("debugtree")),
        ("hg config (all)", lambda: "\n".join(allconfig(ui))),
        ("edenfs rage", _edenfs_rage),
        (
            "environment variables",
            lambda: "\n".join(
                sorted(["{}={}".format(k, v) for k, v in encoding.environ.items()])
            ),
        ),
        ("ssh config", lambda: shcmd("ssh -G hg.vip.facebook.com", check=False)),
        ("debuglocks", lambda: hgcmd("debuglocks")),
        ("x2pagentd info", lambda: checkproxyagentstate(ui)),
        ("sks-agent rage", lambda: sksagentrage(ui)),
    ]

    msg = ""

    footnotes = []
    timeout = opts.get("timeout") or 20

    def _failsafe(gen, timeout=timeout):
        class TimedOut(RuntimeError):
            pass

        def target(result, gen):
            try:
                result.append(gen())
            except TimedOut:
                return
            except Exception as ex:
                index = len(footnotes) + 1
                footnotes.append(
                    "[%d]: %s\n%s\n\n" % (index, str(ex), traceback.format_exc())
                )
                result.append("(Failed. See footnote [%d])" % index)

        result = []
        thread = threading.Thread(target=target, args=(result, gen))
        thread.daemon = True
        thread.start()
        thread.join(timeout)
        if result:
            value = result[0]
            return value
        else:
            if thread.is_alive():
                # Attempt to stop the thread, since hg is not thread safe.
                # There is no pure Python API to interrupt a thread.
                # But CPython C API can do that.
                ctypes.pythonapi.PyThreadState_SetAsyncExc(
                    ctypes.c_long(thread.ident), ctypes.py_object(TimedOut)
                )
            return (
                "(Did not complete in %s seconds, rerun with a larger --timeout to collect this)"
                % timeout
            )

    msg = []
    profile = []
    allstart = time.time()
    for name, gen in basic:
        msg.append("%s: %s\n\n" % (name, _failsafe(gen)))
    profile.append((time.time() - allstart, "basic info", None))
    for name, gen in detailed:
        start = time.time()
        with progress.spinner(ui, name):
            value = _failsafe(gen)
        finish = time.time()
        msg.append(
            "%s: (%.2f s)\n---------------------------\n%s\n\n"
            % (name, finish - start, value)
        )
        profile.append((finish - start, name, value.count("\n")))
    allfinish = time.time()
    profile.append((allfinish - allstart, "total time", None))

    msg.append("hg rage profile:\n")
    width = max([len(name) for _t, name, _l in profile])
    for timetaken, name, lines in reversed(sorted(profile)):
        m = "  %-*s  %8.2f s" % (width + 1, name + ":", timetaken)
        if lines is not None:
            msg.append("%s for %4d lines\n" % (m, lines))
        else:
            msg.append("%s\n" % m)
    msg.append("\n")

    msg.extend(footnotes)
    msg = "".join(msg)

    encoding.encoding = oldencoding
    encoding.encodingmode = oldencodingmode
    return msg