{"record":{"id":"f87fcde4649e0675","repo":"jesseduffield/lazygit","slug":"expected-renamed-file","errorCode":null,"errorMessage":"Expected renamed file","messagePattern":"Expected renamed file","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/commands/git_commands/working_tree.go","lineNumber":89,"sourceCode":"// we accept the current name and the previous name\nfunc (self *WorkingTreeCommands) UnStageFile(paths []string, tracked bool) error {\n\tif tracked {\n\t\treturn self.UnstageTrackedFiles(paths)\n\t}\n\treturn self.UnstageUntrackedFiles(paths)\n}\n\nfunc (self *WorkingTreeCommands) UnstageTrackedFiles(paths []string) error {\n\treturn self.cmd.New(NewGitCmd(\"reset\").Arg(\"HEAD\", \"--\").Arg(paths...).ToArgv()).Run()\n}\n\nfunc (self *WorkingTreeCommands) UnstageUntrackedFiles(paths []string) error {\n\treturn self.cmd.New(NewGitCmd(\"rm\").Arg(\"--cached\", \"--force\", \"--\").Arg(paths...).ToArgv()).Run()\n}\n\nfunc (self *WorkingTreeCommands) BeforeAndAfterFileForRename(file *models.File) (*models.File, *models.File, error) {\n\tif !file.IsRename() {\n\t\treturn nil, nil, errors.New(\"Expected renamed file\")\n\t}\n\n\t// we've got a file that represents a rename from one file to another. Here we will refetch\n\t// all files, passing the --no-renames flag and then recursively call the function\n\t// again for the before file and after file.\n\n\tfilesWithoutRenames := self.fileLoader.GetStatusFiles(GetStatusFileOptions{NoRenames: true})\n\n\tvar beforeFile *models.File\n\tvar afterFile *models.File\n\tfor _, f := range filesWithoutRenames {\n\t\tif f.Path == file.PreviousPath {\n\t\t\tbeforeFile = f\n\t\t}\n\n\t\tif f.Path == file.Path {\n\t\t\tafterFile = f\n\t\t}","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/jesseduffield/lazygit/blob/c477a2959b229fbf3284be0d4d2904ab61ec3c94/pkg/commands/git_commands/working_tree.go#L71-L107","documentation":"BeforeAndAfterFileForRename decomposes a rename entry into its deleted (before) and added (after) halves by refetching status with --no-renames. It requires file.IsRename() (PreviousPath set) up front; passing any other file is a programming/caller error and gets 'Expected renamed file'.","triggerScenarios":"Calling it on a plain modified/added/untracked file; a file model captured before the rename was detected (stale status) so PreviousPath is empty; callers discarding changes that loop over all files instead of only renames.","commonSituations":"Custom commands or controller code that iterates files and unconditionally delegates to the rename path; races between status refresh and user action.","solutions":["Guard the call: only invoke when file.IsRename() is true","Refresh the files model before acting so rename detection (status -z with renames) is current","For non-rename files use DiscardAllFileChanges / regular staging APIs directly"],"exampleFix":"// before\nbefore, after, err := workTree.BeforeAndAfterFileForRename(file)\n\n// after\nif !file.IsRename() {\n    return workTree.DiscardAllFileChanges(file)\n}\nbefore, after, err := workTree.BeforeAndAfterFileForRename(file)","handlingStrategy":"type-guard","validationCode":"if !file.IsRename() {\n    return nil, nil, fmt.Errorf(\"file %s is not a rename\", file.Path)\n}","typeGuard":"func isRenameFile(f *models.File) bool {\n    return f != nil && f.PreviousPath != \"\" && f.IsRename()\n}","tryCatchPattern":"Guard with isRenameFile before calling; when the error surfaces anyway (stale model), refresh file status and re-derive the file object before one retry.","preventionTips":["Refresh the files model before rename-dependent actions","Branch on IsRename() wherever code iterates mixed file lists","Treat a non-rename reaching this API as a caller bug, not a runtime condition"],"tags":["git","rename","validation","working-tree"],"backgroundTag":null,"analyzedSha":"c477a2959b229fbf3284be0d4d2904ab61ec3c94","analyzedAt":"2026-08-15T08:34:32.451Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}