Repository navigation
fix(repo): skill_sha applies a nested repository's ignore rules and digests odd file names by blob id - #390
Conversation
Settles the two points left open in review B10 of intel#323. A file inside a skill or suite that a nested repository (or submodule) ignores by its own .gitignore or info/exclude moved skill_sha and suite_hash, since only the top work tree's ignored paths were read. GitTree.skipped now also asks each directory above a path that holds .git for its own `git ls-files --others --ignored --exclude-standard --directory`, once per nested repository (cached on the tree), and leaves what it lists out. A tree with no nested repository makes no extra git call; the nested .git is still left out, and the nested repository's other files are still hashed by `git hash-object` as before. A file whose name holds a newline, starts with `"` or ends with a CR was hashed by its blob id when committed and unchanged, but by its raw bytes once edited, because `hash-object --stdin-paths` misreads such names. Those names are now passed to `git hash-object --` as arguments, so both states hash the blob id. Both changes move the hash of a tree that holds such files. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
|
✅ OpenCodeReview: Review complete: 0 finding(s) across 3 selected item(s). CI at Review details
|
There was a problem hiding this comment.
🟢 Approval recommended
There are no active review issues requiring changes.
No open issues 🎉
What changed in this PR
0 new findings; nothing to fix.
This follow-up to #323 makes skill_sha and suite_hash respect nested repositories’ ignore rules and consistently hash files with unusual names.
Changes:
- Cache and apply ignore rules from nested repositories and submodules.
- Pass filenames that Git’s stdin parser misreads as arguments, preserving blob-based hashing after edits.
- Add regression tests and document the affected hash changes.
| File | Description |
|---|---|
dokimi/tests/test_repo_config_git_blobs.py |
Tests nested ignore rules and edited-versus-committed hashing for unusual filenames. |
dokimi/dokimi/utils.py |
Applies cached nested ignore rules and hashes unusual filenames through command-line arguments. |
dokimi/changelog.d/20261104_120000_antonwolfy_tree-digest-nested-ignores.md |
Documents both fixes and their effect on existing hashes. |
Review effort: Balanced
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #390 +/- ##
==========================================
+ Coverage 94.75% 94.77% +0.01%
==========================================
Files 352 352
Lines 46498 46455 -43
Branches 7969 7960 -9
==========================================
- Hits 44061 44028 -33
+ Misses 1544 1540 -4
+ Partials 893 887 -6
🚀 New features to boost your workflow:
|
A tree with many newline, quote or CR names passed them all to one git hash-object call; past the command line limit the call failed and the whole batch fell back to raw-byte SHA-256, so an edited tree and its committed twin digested differently. Batch under 24 KB of arguments, and test the batching and the failed-batch fallback. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
napetrov
left a comment
There was a problem hiding this comment.
Verdict: no blocking thread and no inline thread; COMMENT review.
Description nit: the PR body says the odd names go to git hash-object "in one call", but _blob_contents batches them under _ARG_BUDGET (24 000 bytes), so a large tree makes several calls. The output is the same; reword that line.
Coverage: checked the diff, the nested-repository and odd-name paths in dokimi/dokimi/utils.py, and T1 (answered). Not looked at: the 10 digest test modules that fail collection here without regex (not installed, no network), so the test suite was not run; the batch-failure fallback (every odd-named file falls back to its raw sha256 if one batch fails) was not probed.
|
@napetrov the description nit is done: the |
vlad-perevezentsev
left a comment
There was a problem hiding this comment.
Review at d1ca203. The fix does what it says: a nested repository's ignored files leave skill_sha / suite_hash, and odd file names digest by blob id edited or committed. Two small edge cases are in the line comments.
Note: #388 (@napetrov) fixes the same nested-ignore problem in GitTree.skipped differently (it raises when git cannot read a nested repository) and conflicts with this PR in utils.py and test_repo_config_git_blobs.py. Only one of them should go in; please agree which.
What I ran, at d1ca203:
make ci-local --offline: 0 failed, 19 passed, 13 skipped (network-only jobs).- A repro for the top-tracked file under a nested
.gitignoreand for a nested repo with a broken gitfile (see the line comments).
Checked, no issues
- Argument batches: odd names go to
git hash-object --in batches under_ARG_BUDGET; the multi-batch and failed-batch tests cover the fix for the resolved bot thread. - New tests: both fail on the base by the code (the inner
.pycwas hashed; an edited odd name was hashed by raw bytes). - Changelog: says the hash moves once for trees with such files.
Review point (intel#390, utils.py:168): a file the top repository tracks under a nested repository was left out of skill_sha when the nested repository's .gitignore matched it, so an edit to it did not move the hash although the top repository stores the change. The same held for a directory the nested rules ignore that holds a top-tracked file. _index_blobs now also returns every path the top's index holds and each directory above one (GitTree.tracked), and GitTree.skipped applies a nested repository's ignore rules only to paths outside that set. The set comes from `ls-files -s` alone, so a failing `git status` (a HEAD that names a missing commit) still keeps it; only the blob ids are dropped then, as before. The top's own ignore rules and the nested .git are unchanged. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Review point (intel#390, utils.py:181): when git could not read a nested repository (a broken .git file, a corrupt index, an owner git refuses), `_ignored_paths(...) or frozenset()` treated it as ignoring nothing, so its ignored files were hashed only where git failed: one tree, two skill_sha values, and no message. GitTree._inner_ignored now raises OSError naming the directory, which RepoConfig.subjects turns into a ConfigError ("cannot read <dir>: git cannot read this nested repository"). The top's own listing still falls back to no GitTree, as before. Also covers the lines codecov reported: _ignored_paths no longer catches OSError (git_tree's try now holds the call, and a nested one raises as above), and tests pin both failed-listing returns, nested and top. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
@vlad-perevezentsev (#390 (review)): Both edge cases from the line comments are fixed, each in its own commit. A top-tracked file under a nested repository's ignore rule counts again (9316f92). A nested repository git cannot read now raises a |
|
Thanks for the review @napetrov. Nothing further to change from the approval; waiting for the remaining checks. |
Summary
Follow-up to the review of #323, which left two points open about how
skill_shaandsuite_hashhash a git work tree (review). The PR's own notes held the first one as an open question: (a) apply a nested repository's own ignore rules, onegit ls-filesper nested repository, or (b) treat the nested repository as opaque and document that. The author recommended (a), and this PR does (a)."or ends with a CR hashed by its blob id while committed and unchanged, but by its raw bytes once edited or untracked.git hash-object --stdin-pathsmisreads such names, so they were kept away from it.On
main(51bbc4d), with a committed.github/dokimi/config.yaml(subjectskill/a, pathskills/a, suiteskills/a/evals):mainskills/a/vendorisgit inited with.gitignore*.pycand a committedlib.py; thenskills/a/vendor/lib.pycis written (innergit status --porcelainis empty)skill_shasha256:0b004281ad124…→sha256:6bd35a793851e…sha256:0b004281ad124…skills/a/we\nird.txt(one\n) edited totwo\n, then committedsha256:c2e5b88a6a51a…while edited,sha256:bfb3e4a63f74b…once committedsha256:bfb3e4a63f74b…both timesChanges
GitTree.skipped(dokimi/dokimi/utils.py) also checks each directory above a path for a.git(a directory, or a submodule's.gitfile). For each such nested repository it runsgit -C <dir> ls-files --others --ignored --exclude-standard --directory -zonce, caches the result on the tree (GitTree.nested), and leaves out what that listing names below the walk's base, as it already does for the top's ignored paths. A tree with no nested repository makes no extra git call. The nested.gitis still left out. The nested repository's other files, tracked or untracked, are hashed as before:git hash-objectrun from the top, not the nested index. The top's listing moved into_ignored_paths, which both calls use._index_blobsreturns that set (GitTree.tracked) fromgit ls-files -s, so it holds even whengit statusfails.ls-filesfails: a broken.gitfile, a corrupt index, an owner git refuses) raisesOSErrornaming its directory, andRepoConfig.subjects()reports it asConfigError: cannot read <dir>: git cannot read this nested repository. The top's listing failing still gives noGitTree, and the bytes on disk are hashed._blob_contentssends names that--stdin-pathsmisreads (a newline, a leading", a trailing CR) togit hash-object -- <name>…as arguments, in batches under an argument-size budget (_ARG_BUDGET), so a large tree makes several calls. The other names still go on stdin. Such a file now hashes by its blob id whether it is committed or edited. Thefile_contentsdocstring no longer lists such a name as falling back to its bytes.tests/test_repo_config_git_blobs.py:lib.pystill moves it;lib.pycunder*.pyc,build/out.txtunderbuild/) still movesskill_shawhen edited, and still does whengit statusfails, while an untrackedbuild/new.txtstays out;subjects()with thatConfigError, and a top repository whosels-filesfails hashes the bytes on disk;we\nird.txt,"quoted.txt,cr.txt\r, at the top of the work tree, where a top-relative name can start with") digest the same edited and committed. Skipped on Windows, whose file names cannot hold these characters.Risk
skill_shaandsuite_hashmove, once, for a skill or suite in a git work tree that holds either:That is the intended effect. Trees without these do not change, and nothing changes outside a git work tree. A nested repository git cannot read, under a skill or suite, now fails
subjects()with aConfigErrornaming it; onmainits files were hashed. The schema is unchanged. No doc describes the hash input apart from the #323 changelog fragment, so the docstrings and this PR's fragment carry the description.Testing
On a Linux review host, at
8c375c6(onmain5ce3207):make ci-local: 30 passed, 0 failed, 7 skipped. The skipped steps: external-catalog-smoke (needs docker), test-latest, docs deploy and pip-audit report (job conditions false on a PR), cli-surface's Package the result, conda-forge-deps (no micromamba), semgrep.mainand are fixed here. A submodule (its.gitis a file) behaves like thegit inited directory: an ignored.pycleavesskill_sha, and an edit to its tracked file moves it.s/v/lib.pycors/v/build/out.txtunder a nested*.pyc/build/rule left the digest unchanged; a nested repository with a corrupt index was hashed with nothing left out. A broken.gitfile and a refused owner (git'sGIT_TEST_ASSUME_DIFFERENT_OWNER=1) now raise too.main's code: 4 of 4 (the nested-repository test and all three names). Of this round's tests, the three top-tracked cases and the unreadable nested repository fail at d1ca203; the top-tracked cases pass onmain, which never applied a nested repository's rules. The top-listing fallback test pins behaviourmainalready had._inner_skipsnever consults the nested listing: the nested-repository test fails;.githolder is not detected: the nested-repository test fails;--stdin-pathstoo: all three name cases fail;cr.txt\rfails;"quoted.txtfails;skippedapplies the nested rules to a top-tracked path: the three top-tracked cases fail;GitTree.trackedholds files only, not the directories above them: thebuild/out.txtcase fails;GitTree.trackedis dropped whengit statusfails: the failed-status case fails;_ignored_pathsreturns an empty set when git fails: the corrupt-index and top-fallback tests fail.Revert
A plain revert restores the old hashing. Hashes recorded under this change move back for trees that hold such files. No stored record or schema version is left behind.
Checklist
pytestpassesruff,mypy, and schema checks passAGENTS.md.github/.licenserc.yaml)