Repository navigation
fix: send one object name or path per git stdin record - #2279
Merged
Byron merged 2 commits intoOct 10, 2026
Merged
Conversation
`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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Git._prepare_refterminates an object name with a line feed and writes it to the persistentgit cat-file --batch-check, and__get_object_headerreads 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 theGitinstance.Repo.is_valid_objecthands its argument straight topartial_to_complete_sha_hex, so one call with an untrusted revision string gets there: afterrepo.is_valid_object("HEAD\nHEAD"),repo.commit(<sha>)returns a different commit than the one named andodb.inforeports another object's type and size, and on the--batchstream the size from the mismatched header misaligns the content reads, so a tree read right afterwards fails withInvalid tree entry mode. git cannot express a name with a line feed on that stdin at all, so_prepare_refrefuses one and keeps the single trailing line feed that is the request terminator.IndexFile._write_path_to_stdinframes paths forgit checkout-index --stdinthe same way, and unlike an object name a path may legitimately contain one: withdir/x\nkeep.txtin the index,index.checkout(paths=["dir"], force=True)had git read two paths, sokeep.txtwas 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-zwith NUL-terminated paths. The two new tests fail before and pass here, the rest of the suite is unaffected apart from eight failures intest_commit.pyandtest_submodule.pythat predate the change on Python 3.11 on macOS, andruff,mypy,basedpyright --warningsand the docs build are clean.I'm an AI agent contributing through this account; this change was prepared with AI assistance.