@@ -8,9 +8,10 @@ Proposed. 2026-06-20.
88
99## Context
1010
11- The legacy command runner ` libvcs._internal.run.run ` — used by every
12- ` Git ` , ` Hg ` , and ` Svn ` command class via ` .run() ` — does not return what
13- the underlying VCS actually printed. After the process exits it splits
11+ The legacy command runner {func}` libvcs._internal.run.run ` — used by every
12+ {class}` ~libvcs.cmd.git.Git ` , {class}` ~libvcs.cmd.hg.Hg ` , and
13+ {class}` ~libvcs.cmd.svn.Svn ` command class via ` run() ` — does not return
14+ what the underlying VCS actually printed. After the process exits it splits
1415captured stdout into lines, calls ` bytes.strip() ` on each line, drops any
1516line that is empty after stripping, and rejoins with ` \n ` and no trailing
1617newline. stderr is treated the same way and then rejoined with no
@@ -41,8 +42,8 @@ human-readable progress lines, not to capture structured output. The
4142runner's own module docstring already states that it "will be deprecated
4243by ` libvcs._internal.subprocess ` ".
4344
44- ` libvcs._internal.subprocess.SubprocessCommand ` already exists as a thin,
45- typed wrapper that returns a real ` subprocess.CompletedProcess ` with
45+ {class} ` ~ libvcs._internal.subprocess.SubprocessCommand` already exists as a thin,
46+ typed wrapper that returns a real {class} ` subprocess.CompletedProcess ` with
4647separate, untouched ` stdout ` and ` stderr ` . It is bytes-first with opt-in
4748text decoding, and it is currently wired into nothing.
4849
@@ -73,8 +74,8 @@ capture path. This is implemented in two phases so the stable
7374### Phase 2 — pristine structured backend
7475
7576- Route the ` cmd/* ` classes through
76- ` libvcs._internal.subprocess.SubprocessCommand ` , which returns a
77- ` subprocess.CompletedProcess ` (bytes-first, separate
77+ {class} ` ~ libvcs._internal.subprocess.SubprocessCommand` , which returns a
78+ {class} ` subprocess.CompletedProcess ` (bytes-first, separate
7879 stdout/stderr/returncode).
7980- Expose a structured accessor that returns the ` CompletedProcess ` for
8081 callers that want streams, exit code, and exact bytes. Keep
@@ -102,7 +103,7 @@ The decisive measurement: flipping the default to verbatim broke only
102103doctests in ` cmd/git.py ` , ` cmd/hg.py ` , and ` cmd/svn.py ` — example output
103104that gained a trailing newline. No functional test, sync-layer call, or
104105downstream consumer broke, because those already strip where they need a
105- bare value (` vcspull ` , like the sync layer, trims defensively). A global
106+ bare value ([ vcspull] , like the sync layer, trims defensively). A global
106107trailing trim, by contrast, cannot produce an applyable patch: the
107108patch's required final newline is exactly what it strips. So verbatim
108109becomes the default — fixing the original ` git apply ` failure for the
@@ -122,7 +123,7 @@ reflects the true, faithful output.
122123 byte-identical; error messages keep their line structure.
123124- The default now returns output with its trailing newline. Callers that
124125 want a bare value pass ` trim=True ` ; existing consumers are unaffected
125- because the sync layer and ` vcspull ` already strip defensively before
126+ because the sync layer and [ vcspull] already strip defensively before
126127 comparing.
127128- The stderr concatenation defect is removed: error lines keep their
128129 separators. (Phase 2's structured backend additionally avoids the
@@ -150,19 +151,28 @@ reflects the true, faithful output.
150151The decision follows the convergent practice of mature VCS and
151152subprocess-wrapping tools, none of which trim inside the capture path:
152153
153- - ** pip** added a per-call mode (` stdout_only ` ) that returns VCS output
154+ - ** [ pip] ** added a per-call mode (` stdout_only ` ) that returns VCS output
154155 verbatim, and replaced its ` console_to_str ` helper with
155156 ` errors="backslashreplace" ` .
156- - ** uv ** captures into a raw ` Output { stdout, stderr } ` and applies
157+ - ** [ uv ] ** captures into a raw ` Output { stdout, stderr } ` and applies
157158 ` trim_end() ` at each call site for scalar reads.
158- - ** mise** relies on whole-output trailing trim for scalars, decodes
159+ - ** [ mise] ** relies on whole-output trailing trim for scalars, decodes
159160 strictly for data and lossily for stderr.
160- - ** Mercurial** , ** gitoxide** , and ** Jujutsu** are bytes-first and keep
161+ - ** [ Mercurial] ** , ** [ gitoxide] ** , and ** [ Jujutsu] ** are bytes-first and keep
161162 output verbatim; gitoxide treats newline-stripping as a named, opt-in
162163 view, and tracks the missing-final-newline case explicitly.
163- - ** git** itself confirms the constraint: its patch parser requires each
164+ - ** [ git] ** itself confirms the constraint: its patch parser requires each
164165 line, including the last, to be newline-terminated.
165166
166167The lesson shared by all of them: capture pristine, keep streams
167168separate, and make trimming and decoding explicit edge transformations.
168169` SubprocessCommand ` already embodies that shape inside libvcs.
170+
171+ [ vcspull ] : https://vcspull.git-pull.com/
172+ [ pip ] : https://pip.pypa.io/
173+ [ uv ] : https://docs.astral.sh/uv/
174+ [ mise ] : https://mise.jdx.dev/
175+ [ Mercurial ] : https://www.mercurial-scm.org/
176+ [ gitoxide ] : https://github.com/GitoxideLabs/gitoxide
177+ [ Jujutsu ] : https://jj-vcs.github.io/jj/
178+ [ git ] : https://git-scm.com/
0 commit comments