Skip to content

Match caller file paths literally, so file[1].txt no longer also matches file1.txt - #159

Merged
matt-edmondson merged 1 commit into
mainfrom
fix/literal-pathspecs-125
Sep 29, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
fix/literal-pathspecs-125

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #125

Problem

Builders that take a RelativeFilePath passed it to git as a pathspec. Glob characters (*?[]) and a leading : in a real file name were therefore read as pattern syntax:

  • Unstage("file[1].txt") also unstaged file1.txt
  • Unstage(":foo") unstaged foo and left :foo staged
  • Patch().Staged().ForPath("file[1].txt") also returned the hunks for file1.txt

Change

  • GitCommandBuilder gains protected virtual bool PassesPathsLiterally. When it is true, BuildArguments emits the global --literal-pathspecs option before the verb.
  • The audit found six builders that emit caller paths: GitRestoreBuilder, GitAddBuilder, GitPatchBuilder, GitDiffBuilder, GitLogBuilder and GitRevListBuilder.
    • GitRestoreBuilder always passes a path, so it always sets the option.
    • The other five set it only when a path was added, so their argument vectors without paths are unchanged.
  • The option is deliberately not global. git exports it to the processes it starts as GIT_LITERAL_PATHSPECS, so enabling it on commit and similar commands would change how hooks read their own pathspecs.
  • GitCheckoutBuilder's trailing -- carries no path, so it is unaffected.

Tests

Integration/GitLiteralPathspecTests runs against real git and covers:

  • Unstage("file[1].txt") leaves file1.txt staged
  • Unstage(":foo") unstages :foo and not foo. This test is inconclusive on Windows, where a file name can't contain :
  • Add().ForPath("file[1].txt") stages only that file
  • Patch().Staged().ForPath("file[1].txt") returns only that file

With the builder changes reverted, all 4 fail on their assertions. With them in place, the full suite passes: 731 of 731, on git 2.43.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WAdbE4VMmkBh1tzMGpC9Wy


Generated by Claude Code

…hes file1.txt [patch]

Add, Unstage, Patch, Diff, Log and RevList passed their RelativeFilePath
operands to git as pathspecs, so glob characters and a leading colon in a
real file name were read as pattern syntax: Unstage("file[1].txt") also
unstaged file1.txt, Unstage(":foo") unstaged foo, and ForPath returned
another file's hunks. Those builders now pass --literal-pathspecs whenever
they emit a path. It is not set on other commands, because git exports it
to the hooks it runs.

Fixes #125

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WAdbE4VMmkBh1tzMGpC9Wy
@sonarqubecloud

Copy link
Copy Markdown

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.

Unstage("file[1].txt") also unstages file1.txt: paths are passed to git as pathspecs, not literal file names

2 participants