Fix diff paths when diff.srcPrefix and diff.dstPrefix are set

If diff.srcPrefix and diff.dstPrefix are set (git 2.45 and later),
pressing space on a line in the focused main view does nothing. These
configs change the diff of a commit too, so the other commands that
read a path out of a diff go wrong as well.

The configs replace the a/ and b/ in front of the paths of a diff with
any string. We only know how to strip a/ and b/, so the paths we read
out of the diff header don't name a file.

We can't tell from the diff where such a prefix ends, so override the
two configs along with diff.noprefix.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller
2026-10-07 13:53:03 +02:00
co-authored by Claude Opus 5.5
parent c0d230777a
commit e67a85a075
5 changed files with 35 additions and 32 deletions
+7 -7
View File
@@ -267,7 +267,7 @@ func TestCommitShowCmdObj(t *testing.T) {
similarityThreshold: 50,
ignoreWhitespace: false,
diffRendererConfig: nil,
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--"},
expected: []string{"-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--"},
},
{
testName: "Default case with filter path",
@@ -276,7 +276,7 @@ func TestCommitShowCmdObj(t *testing.T) {
similarityThreshold: 50,
ignoreWhitespace: false,
diffRendererConfig: nil,
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--", "file.txt"},
expected: []string{"-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--", "file.txt"},
},
{
testName: "Show diff with custom context size",
@@ -285,7 +285,7 @@ func TestCommitShowCmdObj(t *testing.T) {
similarityThreshold: 50,
ignoreWhitespace: false,
diffRendererConfig: nil,
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--unified=77", "--find-renames=50%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--"},
expected: []string{"-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--unified=77", "--find-renames=50%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--"},
},
{
testName: "Show diff with custom similarity threshold",
@@ -294,7 +294,7 @@ func TestCommitShowCmdObj(t *testing.T) {
similarityThreshold: 33,
ignoreWhitespace: false,
diffRendererConfig: nil,
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--unified=3", "--find-renames=33%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--"},
expected: []string{"-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--unified=3", "--find-renames=33%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--"},
},
{
testName: "Show diff, ignoring whitespace",
@@ -303,7 +303,7 @@ func TestCommitShowCmdObj(t *testing.T) {
similarityThreshold: 50,
ignoreWhitespace: true,
diffRendererConfig: nil,
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--unified=77", "--ignore-all-space", "--find-renames=50%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--"},
expected: []string{"-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--unified=77", "--ignore-all-space", "--find-renames=50%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--"},
},
{
testName: "Show diff with external diff command",
@@ -312,7 +312,7 @@ func TestCommitShowCmdObj(t *testing.T) {
similarityThreshold: 50,
ignoreWhitespace: false,
diffRendererConfig: &config.DiffRendererConfig{Type: "extDiff", Command: "difft --color=always"},
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--"},
expected: []string{"-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "show", "--ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--"},
},
{
testName: "Show diff using git's external diff config",
@@ -321,7 +321,7 @@ func TestCommitShowCmdObj(t *testing.T) {
similarityThreshold: 50,
ignoreWhitespace: false,
diffRendererConfig: &config.DiffRendererConfig{Type: "extDiff"},
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--"},
expected: []string{"-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "show", "--ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--"},
},
}
@@ -144,12 +144,18 @@ func (self *GitCommandBuilder) NoLineEndingConversion() *GitCommandBuilder {
Config("core.safecrlf=false")
}
// ParseableDiffPrefixes makes git put a prefix in front of the paths of a diff
// even if the user has set diff.noprefix. We read the paths of the diffs we show
// out of their headers, and we build patches from them for `git apply`. Both
// rely on there being a prefix to strip.
// ParseableDiffPrefixes makes git put a prefix that we know how to strip in
// front of the paths of a diff. We read the paths of the diffs we show out of
// their headers, and we build patches from them for `git apply`. Both rely on
// such a prefix.
//
// For this we override three configs of the user. diff.noprefix leaves the
// prefix out, and diff.srcPrefix and diff.dstPrefix can set it to any string.
func (self *GitCommandBuilder) ParseableDiffPrefixes() *GitCommandBuilder {
return self.Config("diff.noprefix=false")
return self.
Config("diff.noprefix=false").
Config("diff.srcPrefix=a/").
Config("diff.dstPrefix=b/")
}
func (self *GitCommandBuilder) AddCommonDiffArgs(diffRendererConfigManager *config.DiffRendererConfigManager, userConfig *config.UserConfig, mode DiffMode) *GitCommandBuilder {
+6 -6
View File
@@ -114,7 +114,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
contextSize: 3,
similarityThreshold: 50,
ignoreWhitespace: false,
expected: []string{"git", "-C", "/path/to/worktree", "-c", "diff.noprefix=false", "stash", "show", "--no-ext-diff", "--unified=3", "--find-renames=50%", "-p", "--stat", "-u", "--color=always", "refs/stash@{5}"},
expected: []string{"git", "-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "stash", "show", "--no-ext-diff", "--unified=3", "--find-renames=50%", "-p", "--stat", "-u", "--color=always", "refs/stash@{5}"},
},
{
testName: "Show diff with custom context size",
@@ -122,7 +122,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
contextSize: 77,
similarityThreshold: 50,
ignoreWhitespace: false,
expected: []string{"git", "-C", "/path/to/worktree", "-c", "diff.noprefix=false", "stash", "show", "--no-ext-diff", "--unified=77", "--find-renames=50%", "-p", "--stat", "-u", "--color=always", "refs/stash@{5}"},
expected: []string{"git", "-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "stash", "show", "--no-ext-diff", "--unified=77", "--find-renames=50%", "-p", "--stat", "-u", "--color=always", "refs/stash@{5}"},
},
{
testName: "Show diff with custom similarity threshold",
@@ -130,7 +130,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
contextSize: 3,
similarityThreshold: 33,
ignoreWhitespace: false,
expected: []string{"git", "-C", "/path/to/worktree", "-c", "diff.noprefix=false", "stash", "show", "--no-ext-diff", "--unified=3", "--find-renames=33%", "-p", "--stat", "-u", "--color=always", "refs/stash@{5}"},
expected: []string{"git", "-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "stash", "show", "--no-ext-diff", "--unified=3", "--find-renames=33%", "-p", "--stat", "-u", "--color=always", "refs/stash@{5}"},
},
{
testName: "Show diff with external diff command",
@@ -139,7 +139,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
similarityThreshold: 50,
ignoreWhitespace: false,
diffRendererConfig: &config.DiffRendererConfig{Type: "extDiff", Command: "difft --color=always"},
expected: []string{"git", "-C", "/path/to/worktree", "-c", "diff.noprefix=false", "stash", "show", "--ext-diff", "--unified=3", "--find-renames=50%", "-p", "--stat", "-u", "--color=always", "refs/stash@{5}"},
expected: []string{"git", "-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "stash", "show", "--ext-diff", "--unified=3", "--find-renames=50%", "-p", "--stat", "-u", "--color=always", "refs/stash@{5}"},
},
{
testName: "Show diff using git's external diff config",
@@ -148,7 +148,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
similarityThreshold: 50,
ignoreWhitespace: false,
diffRendererConfig: &config.DiffRendererConfig{Type: "extDiff"},
expected: []string{"git", "-C", "/path/to/worktree", "-c", "diff.noprefix=false", "stash", "show", "--ext-diff", "--unified=3", "--find-renames=50%", "-p", "--stat", "-u", "--color=always", "refs/stash@{5}"},
expected: []string{"git", "-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "stash", "show", "--ext-diff", "--unified=3", "--find-renames=50%", "-p", "--stat", "-u", "--color=always", "refs/stash@{5}"},
},
{
testName: "Ignore whitespace",
@@ -156,7 +156,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
contextSize: 3,
similarityThreshold: 50,
ignoreWhitespace: true,
expected: []string{"git", "-C", "/path/to/worktree", "-c", "diff.noprefix=false", "stash", "show", "--no-ext-diff", "--unified=3", "--ignore-all-space", "--find-renames=50%", "-p", "--stat", "-u", "--color=always", "refs/stash@{5}"},
expected: []string{"git", "-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "stash", "show", "--no-ext-diff", "--unified=3", "--ignore-all-space", "--find-renames=50%", "-p", "--stat", "-u", "--color=always", "refs/stash@{5}"},
},
}
+11 -11
View File
@@ -221,7 +221,7 @@ func TestWorkingTreeDiff(t *testing.T) {
contextSize: 3,
similarityThreshold: 50,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "cached",
@@ -236,7 +236,7 @@ func TestWorkingTreeDiff(t *testing.T) {
contextSize: 3,
similarityThreshold: 50,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--cached", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--cached", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "plain",
@@ -251,7 +251,7 @@ func TestWorkingTreeDiff(t *testing.T) {
contextSize: 3,
similarityThreshold: 50,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=never", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=never", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "File not tracked and file has no staged changes",
@@ -266,7 +266,7 @@ func TestWorkingTreeDiff(t *testing.T) {
contextSize: 3,
similarityThreshold: 50,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--no-index", "--", "/dev/null", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--no-index", "--", "/dev/null", "test.txt"}, expectedResult, nil),
},
{
testName: "Default case (ignore whitespace)",
@@ -281,7 +281,7 @@ func TestWorkingTreeDiff(t *testing.T) {
contextSize: 3,
similarityThreshold: 50,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--ignore-all-space", "--find-renames=50%", "--submodule", "--color=always", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--ignore-all-space", "--find-renames=50%", "--submodule", "--color=always", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "Show diff with custom context size",
@@ -296,7 +296,7 @@ func TestWorkingTreeDiff(t *testing.T) {
contextSize: 17,
similarityThreshold: 50,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=17", "--find-renames=50%", "--submodule", "--color=always", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=17", "--find-renames=50%", "--submodule", "--color=always", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "Show diff with custom similarity threshold",
@@ -311,7 +311,7 @@ func TestWorkingTreeDiff(t *testing.T) {
contextSize: 3,
similarityThreshold: 33,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--find-renames=33%", "--submodule", "--color=always", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--find-renames=33%", "--submodule", "--color=always", "--", "test.txt"}, expectedResult, nil),
},
}
@@ -360,7 +360,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "Show diff with custom context size",
@@ -372,7 +372,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
ignoreWhitespace: false,
contextSize: 123,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=123", "--find-renames=50%", "--submodule", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=123", "--find-renames=50%", "--submodule", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "Default case (ignore whitespace)",
@@ -384,7 +384,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
ignoreWhitespace: true,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--ignore-all-space", "--find-renames=50%", "--submodule", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--ignore-all-space", "--find-renames=50%", "--submodule", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "Renamed file passes both paths so the rename is detected",
@@ -397,7 +397,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "1234567890", "0987654321", "--", "new.txt", "old.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.dstPrefix=b/", "-c", "diff.srcPrefix=a/", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "1234567890", "0987654321", "--", "new.txt", "old.txt"}, expectedResult, nil),
},
}
@@ -40,10 +40,7 @@ var StageDiffLinesWithCustomPrefixes = NewIntegrationTest(NewIntegrationTestArgs
PressPrimaryAction()
t.Views().Files().Lines(
/* EXPECTED:
Contains("MM file1"),
ACTUAL: */
Contains(" M file1"),
)
},
})