`_validate_repo_path` mirrors Git's `verify_path` for tree and index entry
paths, but it only ever saw the path. Git's check there is mode dependent: an
entry that makes `.gitmodules` a symbolic link is refused, because the
submodule configuration would then be read through the link, from outside the
repository. That is why `git update-index --add --cacheinfo
120000,<sha>,.gitmodules` fails with `Invalid path`, `git read-tree` fails with
`invalid path`, and `git fsck --strict` reports `gitmodulesSymlink`.
GitPython accepted such an entry in both directions. `IndexFile.add` with a
`BaseIndexEntry` of mode `120000` and path `.gitmodules` was stored,
`write_tree` serialized the tree, and `IndexFile.commit` wrote a commit Git
refuses to read back and a server with `transfer.fsckObjects` set rejects.
Coming the other way, `read_cache` and `tree_entries_from_data` accepted the
same entry out of an untrusted repository's index or tree.
`_validate_repo_path` now takes the entry mode and, for a symbolic link,
rejects every spelling Git recognizes: `.gitmodules` with trailing spaces or
periods, the HFS form with ignorable code points removed, and the NTFS short
names `gitmod~1` through `gitmod~4` and `gi7eba~1` through `gi7eba~9`. The
mode is passed at the boundaries that have one: `write_cache`, `read_cache`,
`write_tree_from_cache`, `_tree_entry_to_baseindexentry`,
`IndexFile._preprocess_add_items`, `IndexFile.add`, `tree_to_stream`,
`tree_entries_from_data` and `TreeModifier.add`. Paths reached without a mode,
such as the directories walked by `IndexFile._iter_expand_paths`, keep their
previous behavior, and a regular file named `.gitmodules` stays valid.
Checked against `git update-index --add --cacheinfo` on git 2.52.0 for modes
`100644`, `120000`, `160000` and `40000` over the alias corpus: no path is
left that Git rejects and GitPython accepts. Like the existing `.git` rule the
name is tested per component, so a link below a directory spelled like one of
those aliases is refused as well, which Git happens to allow. Adds regression
tests in `test/test_index.py` and `test/test_tree.py`; `mypy`,
`basedpyright --warnings` and `ruff` are clean.
_validate_repo_path mirrors git's verify_path for tree and index entry paths but only ever saw the path, and git's rule there is mode dependent: an entry that makes .gitmodules a symbolic link is refused, since the submodule configuration would then be read through the link, from outside the repository. so IndexFile.add accepted a BaseIndexEntry of mode 120000 named .gitmodules, write_tree serialized the tree, and IndexFile.commit wrote a commit that git read-tree refuses with invalid path and git fsck --strict reports as gitmodulesSymlink, while read_cache and tree_entries_from_data accepted the same entry coming the other way, out of an untrusted repository. the entry mode is now passed down to _validate_repo_path at every boundary that has one, and for a link it rejects each spelling git recognizes: .gitmodules with trailing spaces or periods, the hfs form with ignorable code points removed, and the ntfs short names gitmod~1 through gitmod~4 and gi7eba~1 through gi7eba~9. checked against git update-index --add --cacheinfo on git 2.52.0 for modes 100644, 120000, 160000 and 40000 over the alias corpus, leaving no path that git rejects and gitpython accepts; the name is tested per component like the existing .git rule, so a link below a directory spelled like one of those aliases is refused too.
Note: this change and description were prepared by an AI agent acting for this account's owner.