Skip to content

fix(config): nested-repo ignore rules and per-file eval suites (follow-up to #323) - #388

Open
napetrov wants to merge 20 commits into
intel:mainfrom
napetrov:fix/repo-config-followup-323
Open

napetrov wants to merge 20 commits into
intel:mainfrom
napetrov:fix/repo-config-followup-323

Conversation

@napetrov

@napetrov napetrov commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #323, which left two open questions for the author. Both are now decided and implemented:

  1. A nested git repository inside a skill or suite applies its own ignore rules. Its ignored files no longer move skill_sha or suite_hash. A tracked or untracked change that git keeps still moves them, and a nested .git is still skipped.
  2. A suite may claim individual files under its skill's evals directory. Those files leave skill_sha and move suite_hash. Individual claims of SKILL.md, scripts, references or any other file of the skill stay refused.

Changes

  • dokimi/utils.py: GitTree.skipped asks the innermost nested repository (the deepest directory above the path that holds a .git) what it ignores: one git ls-files --others --ignored --exclude-standard --directory -z per nested repository (a denylist, as for the top, so a name core.ignorecase spells otherwise still counts), cached on the tree snapshot. A path the top's index lists is never dropped by a nested repository's rules. Blob ids stay the parent's. The nested .git is passed to git as its git dir, so one git cannot read fails there instead of git walking up to the enclosing repository. A git failure inside a nested repository (safe.directory, a timeout, a broken .git) raises a config error naming the repository path and git's own message; there is no silent fallback. The same holds for the top work tree: a root with a .git (or below one, on its filesystem) that git cannot read, or whose index it cannot read, raises the same way. A root with no .git above it still hashes the bytes on disk.
  • dokimi/repo_config.py: _check_claims accepts a file match under {skill}/evals; every other individual file claim is still refused, with the evals directory named in the message.
  • dokimi/schemas/dokimi_config.v1.json: the suite description states the rule.
  • changelog.d/20261030_180000_napetrov_feat-repo-config.md: the unreleased feat(repo): dokimi_config.v1 schema and repo_config loader #323 fragment now states both rules (edited in place, the feature has not shipped).
  • Tests: tests/test_repo_config_git_blobs.py (ignored and changed files inside a nested repository, at skills/a/tools and skills/a/evals/tools); tests/test_repo_config.py (individual eval files: hashes and impact; refused outside evals).

Design choices

  • Nested repositories are found on disk, not from ls-files --directory: that lists an untracked directory whole and hides a nested repository inside it.
  • A path is dropped only when it, or a directory above it inside the nested repository, is in that repository's ignored list; a path the top tracks (or a directory on the way to one) is always kept. A failing hash-object (for example a required clean filter) is a config error too.
  • A submodule inside a skill is hashed by the files checked out in it (under its own ignore rules), not by the gitlink commit it is pinned to: a new commit with the same files does not move the hash. Pinned by test_a_submodule_inside_a_nested_repository_is_hashed_by_its_checked_out_files.
  • Individual claims are allowed under {skill}/evals only. A suite that picks files under another directory ({skill}/tests/*.json) is refused; claiming {skill}/tests/** as a whole stays accepted. Possible later widening: accept individual claims under whichever directory the suite names. Kept refused for now.

Evidence

  • Top level, red then green: test_a_broken_top_level_git_is_an_error_not_a_silent_fallback, test_a_directory_inside_a_broken_work_tree_is_an_error_not_a_silent_fallback and test_a_corrupt_index_is_an_error_not_a_silent_fallback fail on c567e37b (no error raised) and pass on the head. test_a_directory_outside_any_work_tree_hashes_its_bytes passes on both (guard). Fixtures that faked a repository with an empty .git (tests/test_repo_config.py, _links.py, _links_v1.py, _globs.py) now run git init, since such a .git is a broken repository under the new rule.
  • Red on the base code (pytest tests/test_repo_config_git_blobs.py tests/test_repo_config.py -k "nested or individual or evals_directory or outside_the_evals"): the two ignored-file nested tests and the per-file eval test fail. The change-moves-hash guards and the refusal tests pass on both. Red on the first fix (test_a_nested_repository_git_cannot_read_is_an_error_not_a_silent_fallback): both cases returned a digest; green after the fix.
  • Bot round on d3d122ef (Copilot and exai inline): an untracked repository inside a nested repository kept its own directory but not the ancestors on the way to it, so the walk never reached it. Red: test_a_change_inside_an_untracked_repository_under_a_nested_repository_moves_a_hash[skills/a/tools] failed on d3d122ef; fixed by keeping every ancestor of a listed entry.
  • Green: pytest tests/test_repo_config*.py tests/test_tree_digest.py: 223 passed, 2 skipped on d3d122ef; pytest tests/test_repo_config_git_blobs.py tests/test_repo_config.py tests/test_tree_digest.py: 178 passed, 2 skipped on the bot-round commit.
  • Full suite on 8d6f05cb's tests (pytest tests, the worktree .venv via uv run --no-project --python 3.13 --with-editable '.[dev]'): 15608 passed, 10 skipped, 4 failed; the test-only refinements after that run were rerun green (test_repo_config*.py, test_tree_digest.py). The 4 are tests/test_repo_typecheck_grader.py (_image_provides('mypy')), which fail the same way on the base run (base-failing.log) in this environment.
  • ruff check, mypy on dokimi/utils.py and dokimi/repo_config.py, check_growth.py, check_comment_blocks.py --base intel/main, audit_test_quality.py, check_changelog_fragment.py --base intel/main: pass.

Merge danger

  • An existing skill_sha or suite_hash of a skill that has a nested repository changes once: an ignored file inside it stopped counting. Values written before are comparable only with values written after this change for skills without nested repositories.
  • A skill with a nested repository that git cannot read (for example a .git owned by another user under safe.directory, or a .git file that points at a missing git dir in a copied checkout) now fails the config load with a message naming its path. Before, it was hashed without its nested rules and nothing was reported.
  • A repository root that git cannot read (a broken .git, safe.directory, a timeout, a stray empty .git with no outer repository above it) now fails the config load the same way. Before, its files were hashed from disk without a word.
  • The schema change is a description only; the field names and shape are unchanged.
  • Individual claims outside evals remain refused. A config that names a non-evals directory per file must claim it whole.

Part of #318. Follow-up to #323.

🤖 Generated with Claude Code

A nested repository inside a skill or suite now applies its own ignore rules (one
git ls-files per nested repository, blob ids as in the parent). Its ignored files no
longer move skill_sha or suite_hash; a tracked or untracked change that git keeps
still does, and a nested .git is still skipped.

A suite may claim individual files under the skill's evals directory (default
evals). Those files leave skill_sha and move suite_hash. Individual claims of
SKILL.md, scripts, references or any other file of the skill stay refused.

Follow-up to intel#323.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
@napetrov
napetrov requested a review from a team as a code owner November 4, 2026 18:00
Copilot AI balanced review requested due to automatic review settings November 4, 2026 18:00

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Untracked nested repositories can omit ancestor directories from the strict skill walk, leaving skill_sha unchanged after file edits; ancestor handling and an intermediate-directory regression test are needed.

1 open issue
What changed in this PR

1 new must-fix finding.

This follow-up to #323 refines repository-config hashing for nested repositories and individual eval files, supporting the per-subject evaluation work in #318.

Changes:

  • Apply nested repositories’ own ignore rules when computing hashes.
  • Allow individual suite file claims under {skill}/evals, while retaining refusals elsewhere.
  • Update regression tests, schema documentation, and the unreleased changelog.
File Description
dokimi/​tests/​test_repo_config.py Tests individual eval claims, hash separation, impact, and refusals.
dokimi/​tests/​test_repo_config_git_blobs.py Tests ignored and retained changes inside nested repositories.
dokimi/​dokimi/​utils.py Adds cached nested-repository filtering for tree digests.
dokimi/​dokimi/​schemas/​dokimi_config.v1.json Documents the individual eval-file claim rule.
dokimi/​dokimi/​repo_config.py Permits individual claims under a skill’s evals directory.
dokimi/​changelog.d/​20261030_180000_napetrov_feat-repo-config.md Describes both updated hashing and suite-claim rules.

Review effort: Balanced

Comment thread dokimi/dokimi/utils.py Outdated
Comment on lines +228 to +233
if text.endswith("/"): # an untracked repository, as ls-files gives it: a directory
dirs.add(path)
continue
files.add(path)
parts = path.split("/")
dirs.update("/".join(parts[:i]) for i in range(depth + 1, len(parts)))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c567e37: every ancestor of a listed nested entry is now kept, so the walk reaches an untracked repository inside a nested one. Red test: test_a_change_inside_an_untracked_repository_under_a_nested_repository_moves_a_hash.

@exai-bot

exai-bot commented Nov 4, 2026 •

Copy link
Copy Markdown

OpenCodeReview: Commit a2246b2 was not reviewed. This PR has reached its review limit (6 reviews).

CI at `a2246b2`: 7 still running, 7 passing, 2 skipped
Check Status
CLI interface diff still running
CodeQL (actions) still running
CodeQL (python) still running
Package install smoke test (py3.11) still running
Tests (py3.11) still running
Tests (py3.14) still running
Windows smoke test (py3.13) still running

To review this commit: re-request review from exai-bot in the Reviewers list.

<details><summary>Last review (8d6f05c): no findings</summary>

Open threads from the bot: 0

</details>

Comment thread dokimi/dokimi/utils.py Outdated
Comment on lines +228 to +230
if text.endswith("/"): # an untracked repository, as ls-files gives it: a directory
dirs.add(path)
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
Must-fix — missing ancestor directories for untracked nested repositories. Trigger: initialize a repository at skills/a/tools, then another untracked repository at skills/a/tools/vendor/child, with vendor containing no ordinary allowed files. Evidence: Git lists vendor/child/; this branch adds child but skips the ancestor-population logic below. Consequently skipped(vendor) is true, and tree_digest(..., strict=True), used for skill_sha in repo_config.py, prunes vendor before reaching child. Impact: edits to child’s non-ignored files do not change skill_sha. Populate ancestor directories for directory entries as well as file entries, and add a regression test for this layout. Workaround: put child directly under tools or add a non-ignored ordinary file under vendor. Severity justification: medium, a bounded but material hash-correctness failure.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c567e37: every ancestor of a listed nested entry is now kept, so the walk reaches an untracked repository inside a nested one. Red test: test_a_change_inside_an_untracked_repository_under_a_nested_repository_moves_a_hash.

A nested repository whose git call fails (safe.directory, a timeout,
a broken or missing .git) used to be hashed without its ignore rules,
silently. Now the nested .git is passed to git as its git dir, so a
.git git cannot read fails there instead of git walking up to the
enclosing repository, and any failed git call raises an OSError that
the config load turns into a ConfigError naming the repository path
and git's message.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
@codecov-commenter

codecov-commenter commented Nov 4, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.78%. Comparing base (7fa98b9) to head (a2246b2).
⚠️ Report is 2 commits behind head on main.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #388   +/-   ##
=======================================
  Coverage   94.78%   94.78%           
=======================================
  Files         353      354    +1     
  Lines       46527    46598   +71     
  Branches     7967     7976    +9     
=======================================
+ Hits        44100    44169   +69     
- Misses       1540     1541    +1     
- Partials      887      888    +1     
Files with missing lines Coverage Δ
dokimi/dokimi/repo_config.py 94.28% <100.00%> (+0.01%) ⬆️
dokimi/dokimi/utils.py 94.01% <100.00%> (+0.14%) ⬆️

... and 11 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…d one

A nested repository lists an untracked repository inside it as a
directory (vendor/inner/). The entry's own directory was kept, but
its ancestors (vendor/) were not, so the strict skill walk skipped
vendor and never reached the files under it. Keep every ancestor of
a listed entry, as is already done for tracked files.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
… too

A root with a .git, or inside a repository, that git cannot read raised nothing and
hashed the bytes on disk, unlike a nested repository. Now git_tree raises OSError
naming the path and git's message, which the config load reports as a ConfigError.
A root with no .git above it on its filesystem (no work tree) still hashes its bytes.

Test fixtures that faked a repository with an empty .git now run git init.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Comment thread dokimi/dokimi/utils.py Outdated
own words (no silent fallback: the digest would count other bytes than git stores)."""
if not _in_work_tree(base):
return None
top_dir = os.fsdecode(_work_git(base, ["rev-parse", "--show-toplevel"], 60).stdout_bytes).strip()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
Must-fix — bind discovery to the nearest .git marker. Trigger: initialize an outer repository and an inner repository, remove inner/.git/HEAD, then call git_tree(inner). Evidence: _in_work_tree accepts the inner marker, but ordinary git -C inner rev-parse --show-toplevel skips the invalid repository directory and discovers the readable outer repository; unlike _nested_allowed, this call does not supply --git-dir. Impact: the promised unreadable-work-tree error is replaced by an outer-tree snapshot, potentially applying the wrong ignore rules and blob metadata. Workaround: repair the inner repository or move it outside the outer work tree. Return the nearest marker from discovery and explicitly validate it with --git-dir before accepting the work tree; add a regression with a readable parent and broken inner HEAD. Severity justification: medium because this is a bounded but material hashing and error-contract failure.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ecbf85b. git_tree now takes the nearest .git (_work_marker) and reads it alone with --git-dir/--work-tree before accepting the work tree.
A broken inner HEAD now raises the same rev-parse failed OSError as the other broken-repo cases, instead of hashing the outer tree.
Regression test_a_broken_inner_repository_under_a_readable_one_is_an_error_not_the_outer_tree: red before the fix, green after.
Full suite: only the 4 known test_repo_typecheck_grader failures.

git_tree found the work tree with a plain rev-parse, which skips a
.git that git cannot read and returns an outer repository. A nested
repository with a broken HEAD was then hashed as part of the outer
tree. The nearest .git is now read alone (--git-dir/--work-tree), and
a failure raises the same OSError as the other broken-repo cases.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
A nested repository inside a nested repository follows the innermost
repository's ignore rules. A submodule inside a skill is hashed by its
checked-out files, not by the gitlink commit. A repository owned by
another user (git's safe.directory refusal, simulated with
GIT_TEST_ASSUME_DIFFERENT_OWNER on the calls that name it) raises
ConfigError. The three pass on the code as it was: no production change.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…tory wildcard

An empty safe.directory entry on the refused calls clears any allow-list, so the test
fails the same way on a runner whose global config is safe.directory = * (CI red on
py3.11 and py3.14: DID NOT RAISE). Red reproduced locally with HOME set to such a config.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

@antonwolfy antonwolfy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nested-repository logic holds up: ignore rules, negations, submodules, nested-in-nested and the new git-failure errors all behave as described, and the three earlier bot points are fixed at this head. One test gap on the new evals claim rule is worth closing before merge, and there is one nit inline.

What I ran, on a Linux review host at ef5fa63:

  • make ci-local: 28 passed, 2 failed, 7 skipped; 15,610 tests passed; changed-line coverage passes. The two failures are not the PR's. test_unbalanced_quote_in_hook_completes_quickly missed its 1 s limit (2.1 s) while the machine was loaded; it passes alone, together with the rest of test_repo_environment.py and test_audit_test_quality.py (680 passed). The benchmark step reports 40 docs under docs/ against a maximum of 39; this PR changes no docs file.
  • Reproductions for the findings, with no model or network access.
  • A mutation run of 36 mutants over utils.py and repo_config.py against the six repo-config and tree-digest test files. The new tests fail on the base modules (14 failed). Most mutants of the ancestor directories, the ls-files flags, the innermost-repo choice, the --git-dir pin and the error raising are killed; seven survive, and the one that decides a verdict is inline.
  • All seven commits carry a Signed-off-by.

Checked, no issues

  • Ignore rules: negations, root-anchored patterns, spaces, newlines and unicode names, tracked files that match an ignore, a .git file in a submodule, a linked worktree and a sparse checkout give the expected skipped set.
  • Error paths: the new OSError from git_tree and tree_digest reaches the config load as a ConfigError; a broken .git inside an excluded directory does not raise.
  • Claims: evals as a link into the skill, Evals, evals-old and .. are all refused.
Bot points I checked that no longer apply at this head (3)
  • Copilot's point on the missing ancestor directories of an untracked repository inside a nested one does not apply: every ancestor of a listed entry is kept at this head.
  • exai-bot's point on the same missing ancestors does not apply: the ancestor loop in _nested_allowed covers slash-terminated entries, and the mutation run killed its removal.
  • exai-bot's point on binding discovery to the nearest .git does not apply: git_tree pins the nearest marker with --git-dir and --work-tree, and a broken inner repository now raises.

Comment thread dokimi/dokimi/repo_config.py Resolved
Comment thread dokimi/dokimi/utils.py Resolved
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Thread: PRRT_kwDOVBg7g860EqNK
Review: PRR_kwDOVBg7g88AAAABWNNG7Q#1

A prefix match on the evals directory (evals-old) or on a claimed directory
name (ref, against refs.md) passed every test. Both boundaries are now pinned:
each mutant fails one of the new cases, and head passes them.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Thread: PRRT_kwDOVBg7g860EqNT: already fixed in bf991bc
Review: PRR_kwDOVBg7g88AAAABWNNG7Q#2: already fixed in bf991bc
Review: PRR_kwDOVBg7g88AAAABWB1DIw#1: already fixed in c567e37

bf991bc (keep spaces in a nested repository's directory name) is the reviewer's
own commit; c567e37 keeps the ancestors of an untracked repository in a nested one.
Empty commit: no code change.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…config-followup-323

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Review: PRR_kwDOVBg7g88AAAABWB1DIw: already fixed in c567e37
Review: PRR_kwDOVBg7g88AAAABWB1jpQ: already fixed in c567e37
Review: PRR_kwDOVBg7g88AAAABWDU_sw: already fixed in ecbf85b
Review: PRR_kwDOVBg7g88AAAABWNNG7Q: already fixed in bf991bc

Empty commit: no code change. The review bodies are covered by their inline
threads (c567e37, ecbf85b) and by the evals and nit points (1c7ae48, bf991bc).

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
@napetrov

napetrov commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

Test gap: fixed in 1c7ae48. Nit: fixed in bf991bc (antonwolfy).

Already fixed in c567e37 (ancestors kept), with a regression test.

Its point (ancestor directories) is fixed in c567e37. Record: dc7b0aa.

Its point (nearest .git) is fixed in ecbf85b, with a regression test.

Not a code claim: the review-limit notice for bf991bc. Nothing to change.

Not a code claim: the Codecov install notice. Nothing to change.

@vlad-perevezentsev vlad-perevezentsev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review at dc7b0aa. The nested ignore rules, the evals claim rule and the new git errors work as described. Two cases are regressions from the base: a nested repository's allowlist drops a file whose name ls-files spells differently (core.ignorecase), and it drops a file the top tracks. The rest are small gaps, all in the line comments. #390 changes the same code with a denylist and conflicts with this branch in utils.py, so one of the two needs to be rebased onto the other.

What I ran, at dc7b0aa:

  • make ci-local --offline: 19 passed, 0 failed, 13 skipped (network and Docker steps).
  • The PR's five test files on the merge-base code: 14 of their tests fail there, and they pass at this head.
  • Repros on the head and on the base, with no model or network: the case-only rename under core.ignorecase, a top-tracked file under a nested repository that ignores it, a top directory whose name ends in a space, and a required clean filter that fails.
  • tree_digest over 5,000 tracked files: 0.77 s against 0.60 s on the base, from the .git lookup on each ancestor. That is acceptable at skill sizes.
  • On Windows I could not run anything: the VM's venv did not resolve from the lock (litellm against filelock), and this PR does not change the lock.

Checked, no issues

  • Earlier threads: the ancestors of an untracked repository inside a nested one are kept (the ancestor loop runs before the slash check), and git_tree pins the nearest .git with --git-dir/--work-tree, so a broken inner repository raises.
  • Claims: the evals-old and refs.md cases pin the boundary of the evals allowance; directory claims are unchanged.
  • Error path: every new OSError reaches subjects() and leaves it as a ConfigError. The only git=True caller is the config loader.
  • Docs and changelog: the unreleased feat-repo-config fragment and the schema description follow the code. The schema change is in a description only.

Comment thread dokimi/dokimi/utils.py Resolved
Comment thread dokimi/dokimi/utils.py Resolved
Comment thread dokimi/dokimi/utils.py Resolved
Comment thread dokimi/dokimi/utils.py Resolved
… win; git errors raise

Address review (vlad-perevezentsev, round 2):
- a nested repository is read with ls-files --others --ignored --directory, as the top
  is, so a case-only rename under core.ignorecase keeps counting
- a path the top's index lists is never dropped by a nested repository's rules
- git_tree keeps a trailing space in --show-toplevel
- hash-object goes through _work_git and raises on failure (a required clean filter)
- _nested_git is gone; the nested calls use _work_git

Thread: PRRT_kwDOVBg7g860MHqZ
Thread: PRRT_kwDOVBg7g860MHqk
Thread: PRRT_kwDOVBg7g860MHqp
Thread: PRRT_kwDOVBg7g860MHq3
Thread: PRRT_kwDOVBg7g860MHq9
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Thread: PRRT_kwDOVBg7g860MHqZ
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Thread: PRRT_kwDOVBg7g860MHq9
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
@napetrov

napetrov commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Waiting on a decision before I change this: #390 and this PR conflict in utils.py. Options: land #388 first and rebase #390, or the reverse. My recommendation: #388 first, since it now uses the same denylist as #390, so #390's rebase is mostly its blob-id part.

GitTree takes main's nested ignore rules (intel#390) in place of this branch's; its covered tests are kept.
Top git failures and a failing hash-object hash the bytes on disk, as intel#390 pins; those tests and fixtures go.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
git_tree stripped --show-toplevel's whole output, so such a top read as no work tree; strip the newline only.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
…ository's rules

git -C walked past a .git it cannot read (no HEAD) and listed the outer repository's ignored paths.
Check that git reads the nested directory as its own top first; its message goes into the error.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
@xaleryb

xaleryb commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Merged main to resolve the conflict with #390, which landed the same nested-ignore feature as item 1. GitTree is #390's code, so there is one implementation, and every test of this PR ran against it.

rstrip("\n") also removed a newline that ends the directory name, so a
readable nested repository named that way failed the config load, and a
top named that way was hashed without its ignore rules.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
@xaleryb

xaleryb commented Nov 6, 2026

Copy link
Copy Markdown
Contributor
  • Ran: head 58044a9 plus the commit below on Python 3.13, with the full suite, ruff, mypy, bandit -ll and the benchmark gate, plus probes of git failures in the top work tree and in nested repositories.
  • A nested repository at skills/a/n<newline> with *.log ignored raised ConfigError git reads .../n instead. Expected a digest, as on main. Our earlier 4cd1e69 handled a trailing space but not a trailing newline.
  • Pushed: a2246b2 uses removesuffix("\n") on --show-toplevel at utils.py:204 and :224, with tests for a name ending in a space or a newline, at the top and nested.
  • Left for the author: the PR body still describes the branch before the merge of main. Removing .git/HEAD at the root, a garbage .git/index or a failing required clean filter gives a digest and no error, as on main, while the body says ConfigError and names three tests that no longer exist. The subjects of d417932 and 728837b carry the same claim into the squash message. After the merge, only a nested repository git cannot read is an error.

@vlad-perevezentsev vlad-perevezentsev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review at a2246b2. With #390 on main, the branch now uses #390's GitTree. That settles the question of which of the two lands first. The code is right. The port adds an error for a nested .git that git reads as another repository, the space and newline fix in --show-toplevel, and the evals claim rule. All five points from my first round are fixed or no longer apply.

Agreed with @xaleryb: the PR body still says that a broken top .git, a garbage index and a failing clean filter give a ConfigError, and it names three tests that are gone. The subjects of d417932 and 728837b say the same. After the merge of main these cases hash the bytes on disk, and only a nested repository git cannot read is an error. Please update the body before the squash.

What I ran, at a2246b2:

  • make ci-local --offline: 19 passed, 0 failed, 13 skipped (network and Docker steps).
  • The PR's test_repo_config.py and test_repo_config_git_blobs.py on the merge-base code: 8 tests fail there (the evals claim, the top name ending in a space or a newline, the nested error cases), and all pass at this head.
  • The repo-config and tree-digest tests on a merge with current main: 258 passed, 2 skipped.
  • Repros for my first-round points: a case-only rename under core.ignorecase, a top-tracked file under a nested repository that ignores it, and a top directory named top . All three behave as the base did before the PR.

Checked, no issues

  • Nested .git git cannot read: _not_own_top asks git for the top from the nested directory and compares real paths. A broken or empty .git now raises and names the directory, as the edited #390 fragment says, where main used the enclosing repository's rules.
  • Claims and docs: the evals allowance, its evals-old and refs.md boundary tests, the schema description and the feat-repo-config fragment agree with the code.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants