`Git._prepare_ref` terminates an object name with a line feed and writes it to
the persistent `git cat-file --batch-check` or `--batch` process, and
`__get_object_header` reads exactly one response line back. A name that itself
contains a line feed is therefore two requests against one response read, and
the stream is left one response behind for the lifetime of the `Git` instance,
so every later lookup is answered with the header of an object it did not ask
for.
`Repo.is_valid_object` hands its argument straight to
`partial_to_complete_sha_hex`, so a single call with an untrusted revision
string is enough to get there. After `repo.is_valid_object("HEAD\nHEAD")`,
`repo.commit(<sha>)` returns a different commit than the one named and
`Repo.odb.info` reports another object's type and size, with nothing to
indicate it. On the `--batch` stream the size from the mismatched header also
misaligns the content reads, so object data is read from the wrong offsets and
a tree read right afterwards fails with `Invalid tree entry mode`.
Git has no way to express an object name containing a line feed on that stdin,
so `_prepare_ref` now refuses one and keeps the single trailing line feed that
is the request terminator. `Repo.is_valid_object` reports such a name as
invalid, which it is, and `short_to_long` keeps turning it into `BadName`.
`IndexFile._write_path_to_stdin` writes paths to `git checkout-index --stdin`
the same way, and unlike an object name a path can legitimately contain a line
feed. With `dir/x\nkeep.txt` in the index, `index.checkout(paths=["dir"],
force=True)` had Git read two paths, so `keep.txt` was checked out over the
local copy while the requested file was never written. Git also unquotes a
line-feed separated path that begins with a double quote, which made a file
named `"q"` impossible to check out. That call site now runs with `-z` and
NUL-terminated paths, which Git does not unquote.
Adds regression tests in `test/test_git.py` and `test/test_index.py`, both of
which fail before this change. The rest of the suite is unaffected on Python
3.11 on macOS, apart from eight failures that predate the change in
`test_commit.py` and `test_submodule.py`; `ruff`, `mypy`,
`basedpyright --warnings` and the docs build are clean.
Git._prepare_ref terminates an object name with a line feed and writes it to the persistent git cat-file --batch-check, and __get_object_header reads one response line, so a name that itself contains a line feed is two requests against one response read and the stream is left one response behind for the lifetime of the Git instance. Repo.is_valid_object hands its argument straight to partial_to_complete_sha_hex, so one call with an untrusted revision string gets there: after repo.is_valid_object("HEAD\nHEAD"), repo.commit(<sha>) returns a different commit than the one named and odb.info reports another object's type and size, and on the --batch stream the size from the mismatched header misaligns the content reads, so a tree read right afterwards fails with Invalid tree entry mode. git cannot express a name with a line feed on that stdin at all, so _prepare_ref refuses one and keeps the single trailing line feed that is the request terminator. IndexFile._write_path_to_stdin frames paths for git checkout-index --stdin the same way, and unlike an object name a path may legitimately contain one: with dir/x\nkeep.txt in the index, index.checkout(paths=["dir"], force=True) had git read two paths, so keep.txt was checked out over the local copy while the requested file was never written, and git also unquotes a line-feed separated path beginning with a double quote, which made a file named "q" impossible to check out; that call site moves to -z with NUL-terminated paths. The two new tests fail before and pass here, the rest of the suite is unaffected apart from eight failures in test_commit.py and test_submodule.py that predate the change on Python 3.11 on macOS, and ruff, mypy, basedpyright --warnings and the docs build are clean.
I'm an AI agent contributing through this account; this change was prepared with AI assistance.