Skip to content

fix: preserve literal paths in Git change discovery#618

Draft
teilomillet wants to merge 1 commit into
tirth8205:mainfrom
teilomillet:dev/preserve-git-change-paths
Draft

fix: preserve literal paths in Git change discovery#618
teilomillet wants to merge 1 commit into
tirth8205:mainfrom
teilomillet:dev/preserve-git-change-paths

Conversation

@teilomillet

@teilomillet teilomillet commented Jul 10, 2026

Copy link
Copy Markdown

Linked issue

No exact issue exists for these Git discovery functions; the defect was found with Ordeal while auditing the current Python Trending surface. #497 is related Unicode-path work, but it concerns generated host configuration rather than incremental Git output. PR #566 overlaps these subprocess blocks while addressing decode crashes, but its replacement decoding does not preserve Git's quoted paths.

What & why

code-review-graph keeps a map of a repository so its review tools know which files changed and what those changes may affect. When someone edited a valid source file such as café.py, Git sometimes reported the name in an encoded form. The updater then searched for that encoded text instead of the real filename, so it could silently miss the changed file. This patch reads Git's unambiguous machine format, allowing the updater to find the real file while preserving its existing rename and copy behavior.

Before this change, both the working-tree and commit-diff paths produced:

['"caf\\303\\251.py"']

instead of:

["café.py"]

Downstream incremental parsing then looked up a filename that did not exist and could silently miss the changed source file. The line-based status parser also treated a literal -> inside a filename as a rename delimiter and truncated that path.

Git quotes unusual paths in its newline-delimited human-readable output. This change requests NUL-delimited output from git diff --name-only -z and git status --porcelain=v1 -z, keeps the subprocess output as bytes, and decodes each complete path with os.fsdecode. Rename and copy records retain the existing destination-only behavior by skipping the following source-path record.

The changelog now records both corrected behaviors. Ordinary ASCII paths and the existing rename behavior remain unchanged; no schema or migration change is required.

Ordeal evidence

A contract-specific Ordeal fuzz property generated valid non-ASCII Python filenames and required each Git discovery API to return the literal path.

  • get_staged_and_unstaged: 12 examples, minimized failure {"filename": "café.py"}.
  • get_changed_files: 12 examples, minimized failure {"filename": "café.py"}.
  • Direct reproduction outside the discovery harness returned ['"caf\\303\\251.py"'] from both APIs.
  • The exact target-native regressions failed before the production change and passed afterward.
  • Both Ordeal properties passed all 12 examples after the fix.

The semantic-delta check also covers simple paths, destination-only rename and copy records, and a literal path -> literal.py filename.

How it was tested

uv lock --check
uv run pytest tests/test_integration_git.py -k unicode_path -q
uv run pytest tests/test_incremental.py::TestGitOperations -q
uv run pytest tests/ --tb=short -q
uv run pytest --tb=short -q --cov=code_review_graph --cov-report=term-missing --cov-fail-under=65
uv run ruff check code_review_graph/
uv run ruff check tests/test_integration_git.py
uv run mypy code_review_graph/ --ignore-missing-imports --no-strict-optional
uv run bandit -r code_review_graph/ -c pyproject.toml
git diff --check

Results:

  • Full suite: 1,386 passed, 1 skipped, 2 xpassed.
  • Coverage suite: 73.50%, above the required 65% threshold.
  • Ruff, mypy, Bandit, lockfile, and diff checks passed.
  • A self-hosted code-review-graph build parsed all 179 files, and detect-changes --base origin/main --brief analyzed the intended four-file patch.

The local run used macOS and Python 3.13. Repository CI will provide the configured Python 3.10-3.13 matrix. The coverage run emitted existing SQLite ResourceWarnings in unrelated parser, visualization, and wiki tests; they did not fail the suite.

Checklist

  • Tests added for new functionality
  • All tests pass: uv run pytest tests/ --tb=short -q
  • Linting passes: uv run ruff check code_review_graph/
  • Type checking passes: uv run mypy code_review_graph/ --ignore-missing-imports --no-strict-optional
  • Lines are at most 100 characters
  • Docs updated where behavior changed (CHANGELOG.md)

Read NUL-delimited Git output so incremental updates keep non-ASCII
and arrow-containing filenames intact while retaining destination-only
rename and copy semantics.
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.

1 participant