Skip to content

With LocalPath set to a subdirectory, Unstage and Apply().ToIndex() exit 0 and change nothing, and ForPath filters return empty #178

Description

@matt-edmondson

What's wrong

GitRepository.LocalPath is a public init property, and nothing requires it to be the top of the working tree. IsClonedAsync uses rev-parse --is-inside-work-tree, which also answers true for a subdirectory, so a repository opened at a subdirectory looks valid.

Every verb then runs git -C <LocalPath> --literal-pathspecs … (GitIntegration/GitRepository.cs, Builders/GitCommandBuilder.cs). The paths are mismatched:

  • Status(), Diff() and Patch() report paths relative to the repository root, which is how RelativeFilePath is documented.
  • Git reads pathspecs relative to the -C directory.
  • git apply run from a subdirectory silently skips any patched path outside that directory, and still exits 0.

Failure scenario (reproduced with git 2.43)

The repo contains root.txt and sub/s.txt. Open it with new GitRepository { LocalPath = <repo>/sub }:

  • Apply: Apply(patch).ToIndex() with a patch touching root.txt runs git -C sub apply --cached <tmp>. It exits 0, and git diff --cached is empty. The caller is told the hunk was staged.
  • Unstage: stage sub/s.txt, then call Unstage("sub/s.txt"), the path exactly as Status() reported it. That runs git -C sub --literal-pathspecs reset -q -- sub/s.txt, which exits 0, and the file is still staged.
  • Log/Diff/Patch/RevList: .ForPath("sub/s.txt") silently returns empty results.
  • Add: Add().ForPath("sub/s.txt") at least fails loudly (pathspec did not match, exit 128).

Suggested fix

Either:

  • (a) Resolve paths from the root whatever -C is: use :(top,literal)<path> pathspec magic in place of the global --literal-pathspecs, and run apply from rev-parse --show-toplevel; or
  • (b) Make the requirement explicit: document that LocalPath must be the working-tree root, and enforce it. For example, verbs that take paths would probe rev-parse --show-prefix and throw when it is non-empty.

Acceptance criteria

  • An integration test opens a repository at a subdirectory. It shows that Unstage, Apply().ToIndex() and the ForPath filters either act on root-relative paths correctly or fail loudly.
  • None of these operations reports success while making no change.

Activity

  1. matt-edmondson commented on Oct 6, 2026

    @matt-edmondson
    ContributorAuthor

    Triage


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions