From 6925f21c01a3589c84815bd364274207e30245c4 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 19c07ece138..36ab8ef6742 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 80373eef1b3..ec1f502f1a8 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 {