Repository navigation
fix(repo): unquote the filename line in Repo.blame_incremental - #2314
Closed
Keerthana-64 wants to merge 1 commit into
Closed
Keerthana-64 wants to merge 1 commit into
Keerthana-64 wants to merge 1 commit into
Conversation
`Repo.blame_incremental` took `BlameEntry.orig_path` from the `filename` line of `git blame --incremental` verbatim. Git writes that line with `write_name_quoted`, so a path containing a double quote, a backslash, a control character or, with the default `core.quotePath=true`, any non-ASCII byte is C-quoted: a line attributed to `café.txt` came back as `orig_path == '"caf\303\251.txt"'` and `"quoted".txt` as `'"\\"quoted\\".txt"'`, names that exist nowhere in the repository. The quoted form reaches callers whenever the blamed file was renamed from such a name, which the author of the history chooses. Decode a quoted `filename` value with the existing `_unquote_path` helper from `git.diff`, which already reverses Git's quoting for diff headers; unquoted values are unchanged. The non-incremental porcelain parser in `Repo.blame` reads the same line but does not expose it, so it needs no change. Adds a `blame-quoted-filename` case to `tests/fixtures/repo.sh` and `test_blame_incremental_unquotes_filenames` in `test/test_repo.py`, which fails before the change and passes after on both the CLI and the Gix backend. Ruff, mypy and ShellCheck pass.
1 task done
Byron
added a commit
that referenced
this pull request
Oct 27, 2026
<!-- agent --> `Repo.blame_incremental()` copied Git's `filename` field into `BlameEntry.orig_path` without interpreting its C-style quoting. After renaming `café.txt` to `current.txt`, callers received `"caf\303\251.txt"` instead of `café.txt`, so following an attribution back to its original tree entry failed. Quotes, backslashes and control characters had the same problem, even with `core.quotePath=false`. Decode quoted filenames with the existing `git.diff._unquote_path()` helper before constructing each entry. This shared point handles both newly encountered and cached commits, leaves unquoted names unchanged, and retains `safe_decode()`'s byte-preserving `surrogateescape` behavior. `Repo.blame(..., incremental=True)` receives the same correction. Blame continues to use Git's CLI on both backends; this is an output-parser fix, not a new Gix implementation. Add an independently written tree-based fixture and regression covering eight historical filenames with both `core.quotePath` settings. Renaming then editing the middle line produces two separated entries attributed to the same original commit, exercising both parser branches. Check the commit, original path and both line ranges through both public entry points. Tree construction avoids platform restrictions on checking out control-character and invalid-UTF-8 filenames. The regression failed in ten quoted-filename subcases on each backend before the fix and passes afterward. Repository and diff tests pass on both backends; Ruff lint/format, mypy and ShellCheck also pass. Git evidence: the comparison binary is `git version 2.54.0 (Apple Git-157)`. Reference checkout revision `6de20f6092dcf9bdb1c8efe03db4b70c82b423dd` documents the terminating `filename` field in `Documentation/git-blame.adoc`; its `builtin/blame.c:write_filename_info()` uses `write_name_quoted()`. The regression's real `git blame --incremental HEAD -- current.txt` output quotes the historical path in both entries for the original commit, matching that format. Independent solution to the problem reported and investigated by `Keerthana-64` in #2314. No implementation or tests were taken from that PR. Assisted-by: GPT 6.1 Sol Co-authored-by: GPT 6.1 Sol <codex@openai.com>
Member
|
This PR is violating the contribution guidelines (impersonation) which require the agent to identify themselves, with the PR text clearly being LLM generated. This is why it will be regenerated and superseded. Put yourself into my shoes, I might interact with a human, but there is no use in responding to an agent. Not making clear who is behind the PR forces me to fallback to assume it's all made without human interaction, a bot on a cron job, who has been told to not follow self-disclosure or intentionally impersonate. This isn't acceptable. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
repo.blame_incrementalcopies thefilenameline ofgit blame --incrementalintoBlameEntry.orig_pathas is, but git writes that line withwrite_name_quoted, so a name with a double quote, a backslash, a control character or, under the defaultcore.quotePath=true, any non-ASCII byte arrives C-quoted: a line that came fromcafé.txtis reported withorig_path == '"caf\303\251.txt"', a name that exists nowhere in the repository. the quoted form shows up whenever the blamed file was renamed from such a name, which whoever wrote the history picks, so a tool that followsorig_pathback to the origin file (ownership or license attribution, provenance checks) looks up a path that does not exist. decode a quoted value with the_unquote_pathhelpergit.diffalready uses for patch headers; unquoted names are unchanged, andrepo.blamereads the same line without exposing it, so it is untouched. this is the same quoting #2298 handled forcheck-ignoreoutput, in a different parser. the new fixture case and test intest/test_repo.pyfail before and pass after on both backends; ruff, mypy and shellcheck pass.