Skip to content

Git.run() corrupts non-UTF-8 output — backslashreplace decode is unrecoverable #541

Description

@k0pernikus

Git(path=...).run(["diff", ...]) rewrites every byte that is not valid UTF-8 into the literal four-character text \xNN: captured output funnels through console_to_str, whose fallback is s.decode("utf_8", errors="backslashreplace"), and every public command surface is typed -> str. backslashreplace is not round-trippable, so the original bytes are unrecoverable, and no bytes-returning API exists. A captured diff of latin-1 content is no longer an applicable patch — git apply rejects it (patch does not apply); a blob read via show cannot round-trip at all.

Expected: captured output is byte-faithful to what git wrote — the same output-fidelity contract as #536, one layer down. (The #538 trim fix holds on 0.45.0: whitespace and the trailing newline survive; the decode is the remaining gap.)

Reproducible example: https://github.com/k0pernikus/libvcs-non-utf8-on-diff

uv run pytest (or mise run test) — one test asserts the subprocess git diff output round-trips the patch byte-exact (passes), one asserts the libvcs output against the same expected patch (fails: every 0xE9/0xE8 byte arrives as the literal text \xe9/\xe8).

run() already keeps the pipe in bytes mode internally (text=False, # Keep in bytes mode to preserve \r properly) — fidelity is lost only at the return boundary. A bytes-mode surface, or an errors=/encoding= knob honored at the final decode, would close this.

Environment: libvcs 0.45.0, Python 3.15, any recent git on a UTF-8 locale

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions