Skip to content

Run git in literal-pathspec mode - #326

Merged
umputun merged 1 commit into
umputun:masterfrom
paskal:fix/literal-pathspecs
Aug 19, 2026
Merged

Run git in literal-pathspec mode#326
umputun merged 1 commit into
umputun:masterfrom
paskal:fix/literal-pathspecs

Conversation

@paskal

@paskal paskal commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

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:

git init repo && cd repo
printf 'real\n'  > README.md
printf 'magic\n' > ':(top)README.md'
git add -A && git commit -m init
printf 'real2\n'  >> README.md
printf 'magic2\n' >> ':(top)README.md'

git diff --name-only -- ':(top)README.md'
# README.md            <- the wrong file
GIT_LITERAL_PATHSPECS=1 git diff --name-only -- ':(top)README.md'
# :(top)README.md      <- the file that was asked for

A file named *.txt is worse: the pathspec matches every .txt file in the tree, so the diff pane shows several files concatenated. The reviewer sees another file's diff, or an empty one, and app/history writes the same wrong content into the saved review history. git blame is unaffected, since it takes its single file operand literally.

Every git invocation in the Git renderer now runs with the environment GitEnv builds, and app/history builds its diff command with the same one. GIT_GLOB_PATHSPECS and GIT_ICASE_PATHSPECS are dropped from it, because git refuses to combine either with literal mode:

fatal: global 'literal' pathspec setting is incompatible with all other global pathspec settings

so leaving an inherited one in place would abort every command for anyone who has it set. GIT_NOGLOB_PATHSPECS is 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:34 runs git ls-files -z with no path operand, history.go:160 runs git rev-parse --short HEAD, and compare.go:42 runs git 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 *.txt beside star.txt, git diff --no-index -- '*.txt' star.txt reports *.txt => star.txt with 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.

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.
@paskal
paskal requested a review from umputun as a code owner August 19, 2026 15:48
Copilot AI lite review requested due to automatic review settings August 19, 2026 15:48

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.

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 force GIT_LITERAL_PATHSPECS=1 while 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 thread app/diff/diff.go
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
umputun merged commit 1771be5 into umputun:master Aug 19, 2026
2 checks passed
@paskal
paskal deleted the fix/literal-pathspecs branch August 20, 2026 06:59
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.

3 participants