From e6f3313d9a9f3c1c23e7069417c002699384f468 Mon Sep 17 00:00:00 2001 From: Karen Grigoryan Date: Sat, 13 Sep 2025 12:59:27 +0200 Subject: [PATCH] Fix support for Git copy status when status.renames=copies Fixes #4890 When Git is configured with status.renames=copies, it can produce status codes starting with "C" (copy) in addition to "R" (rename). The file loader was only checking for "R" prefixes, causing copy operations to be parsed incorrectly and breaking file staging. This fix extends the status parser to handle both "R" and "C" prefixes, ensuring proper support for Git's copy detection feature. Fixes file staging issues when using status.renames=copies configuration. --- pkg/commands/git_commands/file_loader.go | 4 +- pkg/commands/git_commands/file_loader_test.go | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/pkg/commands/git_commands/file_loader.go b/pkg/commands/git_commands/file_loader.go index 19c07ece1..36ab8ef67 100644 --- a/pkg/commands/git_commands/file_loader.go +++ b/pkg/commands/git_commands/file_loader.go @@ -201,8 +201,8 @@ func (self *FileLoader) gitStatus(opts GitStatusOptions) ([]FileStatus, error) { PreviousPath: "", } - if strings.HasPrefix(status.Change, "R") { - // if a line starts with 'R' then the next line is the original file. + if strings.HasPrefix(status.Change, "R") || strings.HasPrefix(status.Change, "C") { + // if a line starts with 'R' (rename) or 'C' (copy) then the next line is the original file. status.PreviousPath = splitLines[i+1] status.StatusString = fmt.Sprintf("%s %s -> %s", status.Change, status.PreviousPath, status.Path) i++ diff --git a/pkg/commands/git_commands/file_loader_test.go b/pkg/commands/git_commands/file_loader_test.go index 80373eef1..ec1f502f1 100644 --- a/pkg/commands/git_commands/file_loader_test.go +++ b/pkg/commands/git_commands/file_loader_test.go @@ -192,6 +192,43 @@ func TestFileGetStatusFiles(t *testing.T) { }, }, }, + { + testName: "Copied files", + similarityThreshold: 50, + runner: oscommands.NewFakeRunner(t). + ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"}, + "C copy1.txt\x00original.txt\x00CM copy2.txt\x00original.txt", + nil, + ), + expectedFiles: []*models.File{ + { + Path: "copy1.txt", + PreviousPath: "original.txt", + HasStagedChanges: true, + HasUnstagedChanges: false, + Tracked: true, + Added: false, + Deleted: false, + HasMergeConflicts: false, + HasInlineMergeConflicts: false, + DisplayString: "C original.txt -> copy1.txt", + ShortStatus: "C ", + }, + { + Path: "copy2.txt", + PreviousPath: "original.txt", + HasStagedChanges: true, + HasUnstagedChanges: true, + Tracked: true, + Added: false, + Deleted: false, + HasMergeConflicts: false, + HasInlineMergeConflicts: false, + DisplayString: "CM original.txt -> copy2.txt", + ShortStatus: "CM", + }, + }, + }, } for _, s := range scenarios {