Skip to content

fix: send one object name or path per git stdin record - #2279

Merged
Byron merged 2 commits into
gitpython-developers:mainfrom
Keerthana-64:stdin-record-framing
Oct 10, 2026
Merged

Byron merged 2 commits into
gitpython-developers:mainfrom
Keerthana-64:stdin-record-framing

Conversation

@Keerthana-64

Copy link
Copy Markdown
Contributor

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.

Keerthana-64 and others added 2 commits October 10, 2026 01:38
`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.
- also update agents.md for clearer separation of speech, as an experiment really.

Assisted-by: GPT 6.1 Sol
Co-authored-by: GPT 6.1 Sol <codex@openai.com>
@Byron
Byron merged commit 804bc80 into gitpython-developers:main Oct 10, 2026
47 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants