Run git in literal-pathspec mode - #326
Merged
Merged
Conversation
Path arguments reach git straight from the VCS listing or from the user, so
a tracked file whose name begins with ":(" or contains a glob character was
parsed as a pathspec expression instead of selecting itself. `git diff --
':(top)README.md'` resolved to README.md, and a file named "*.txt" matched
every .txt file in the tree, which showed the reviewer another file's diff or
an empty one and wrote the same wrong content into saved review history.
Every git invocation now runs with the environment GitEnv builds: literal
pathspec mode on, and GIT_GLOB_PATHSPECS / GIT_ICASE_PATHSPECS dropped,
because git refuses to combine either of those with literal mode and would
otherwise abort with "global 'literal' pathspec setting is incompatible with
all other global pathspec settings" for anyone who has one of them set. The
history package builds its diff command with the same environment. The
untracked-rename path already set literal mode locally; that special case is
gone now the shared helper covers it. No pathspec magic is used anywhere in
the codebase, so nothing else changes.
There was a problem hiding this comment.
Pull request overview
This PR hardens revdiff’s Git integration against “magic” pathspec parsing by forcing literal pathspec handling for Git commands that consume file paths coming from the working tree/UI, preventing incorrect diffs (and incorrect history captures) for tracked files whose names look like pathspec expressions (e.g. :(top)f.txt, *.txt, [x].txt).
Changes:
- Introduces
diff.GitEnv()to forceGIT_LITERAL_PATHSPECS=1while stripping inherited conflicting global pathspec env vars. - Routes Git renderer executions and history diff capture through that environment.
- Adds regression tests covering magic-shaped filenames, list→diff round-tripping, history diff capture, and conflicting env var handling.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| app/diff/diff.go | Adds GitEnv() and wires it into Git command execution helpers. |
| app/diff/diff_test.go | Adds tests validating literal-pathspec behavior and env-conflict stripping. |
| app/history/history.go | Ensures history’s git diff capture runs with diff.GitEnv(). |
| app/history/history_test.go | Adds a history capture regression test for a magic-prefixed filename. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+61
to
+67
| // GitEnv returns the environment for a child git process: the current environment | ||
| // with literal pathspec handling forced on and the settings that conflict with it | ||
| // removed. Paths reach git straight from the VCS listing or from the user, so a | ||
| // file actually named ":(top)x" or "*.go" must select itself rather than be parsed | ||
| // as a pathspec expression; nothing here relies on pathspec magic. Dropping the | ||
| // conflicting entries is harmless, since neither affects a literal path. | ||
| func GitEnv() []string { |
umputun
approved these changes
Aug 19, 2026
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.
Path arguments reach git straight from the VCS listing or from the user, so a tracked file whose name begins with
:(or contains a glob character was parsed as a pathspec expression rather than selecting itself. On current master:A file named
*.txtis worse: the pathspec matches every.txtfile in the tree, so the diff pane shows several files concatenated. The reviewer sees another file's diff, or an empty one, andapp/historywrites the same wrong content into the saved review history.git blameis unaffected, since it takes its single file operand literally.Every git invocation in the
Gitrenderer now runs with the environmentGitEnvbuilds, andapp/historybuilds its diff command with the same one.GIT_GLOB_PATHSPECSandGIT_ICASE_PATHSPECSare dropped from it, because git refuses to combine either with literal mode:so leaving an inherited one in place would abort every command for anyone who has it set.
GIT_NOGLOB_PATHSPECSis compatible and stays. Neither dropped setting affects a literal path, so nothing is lost.The three git call sites outside that path are left alone because none of them takes a pathspec:
directory.go:34runsgit ls-files -zwith no path operand,history.go:160runsgit rev-parse --short HEAD, andcompare.go:42runsgit diff --no-index, which treats its two operands as filesystem paths. That last one is the only non-obvious case, so I checked it directly: with a file literally named*.txtbesidestar.txt,git diff --no-index -- '*.txt' star.txtreports*.txt => star.txtwith or without literal mode.The untracked-rename path already set literal mode locally through
renameIndexEnv; that special case is gone now the shared helper covers it. Nothing in the codebase uses pathspec magic, so no other behaviour changes.Tests cover the three filename shapes (
:(top)f.txt,*.txt,[x].txt), the listing-to-diff round trip, the history diff capture, and both conflicting environment variables. Each fails on master for the reason it names.