jesseduffield/lazygit · error
Expected renamed file
Error message
Expected renamed file
What it means
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'.
Source
Thrown at pkg/commands/git_commands/working_tree.go:89
// we accept the current name and the previous name
func (self *WorkingTreeCommands) UnStageFile(paths []string, tracked bool) error {
if tracked {
return self.UnstageTrackedFiles(paths)
}
return self.UnstageUntrackedFiles(paths)
}
func (self *WorkingTreeCommands) UnstageTrackedFiles(paths []string) error {
return self.cmd.New(NewGitCmd("reset").Arg("HEAD", "--").Arg(paths...).ToArgv()).Run()
}
func (self *WorkingTreeCommands) UnstageUntrackedFiles(paths []string) error {
return self.cmd.New(NewGitCmd("rm").Arg("--cached", "--force", "--").Arg(paths...).ToArgv()).Run()
}
func (self *WorkingTreeCommands) BeforeAndAfterFileForRename(file *models.File) (*models.File, *models.File, error) {
if !file.IsRename() {
return nil, nil, errors.New("Expected renamed file")
}
// we've got a file that represents a rename from one file to another. Here we will refetch
// all files, passing the --no-renames flag and then recursively call the function
// again for the before file and after file.
filesWithoutRenames := self.fileLoader.GetStatusFiles(GetStatusFileOptions{NoRenames: true})
var beforeFile *models.File
var afterFile *models.File
for _, f := range filesWithoutRenames {
if f.Path == file.PreviousPath {
beforeFile = f
}
if f.Path == file.Path {
afterFile = f
}View on GitHub (pinned to c477a2959b)
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
Example fix
// before
before, after, err := workTree.BeforeAndAfterFileForRename(file)
// after
if !file.IsRename() {
return workTree.DiscardAllFileChanges(file)
}
before, after, err := workTree.BeforeAndAfterFileForRename(file) Defensive patterns
Strategy: type-guard
Validate before calling
if !file.IsRename() {
return nil, nil, fmt.Errorf("file %s is not a rename", file.Path)
} Type guard
func isRenameFile(f *models.File) bool {
return f != nil && f.PreviousPath != "" && f.IsRename()
} Try / catch
Guard with isRenameFile before calling; when the error surfaces anyway (stale model), refresh file status and re-derive the file object before one retry.
Prevention
- 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
When it happens
Trigger: 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.
Common situations: Custom commands or controller code that iterates files and unconditionally delegates to the rename path; races between status refresh and user action.
Related errors
- Could not find deleted file or new file for file rename
- Must specify a remote if specifying a branch
- Nested rename found
- You cannot commit without a commit message
- No changed files
AI-assisted analysis of jesseduffield/lazygit@c477a2959b (2026-08-15).
Data as JSON: /api/errors/f87fcde4649e0675.
Report an issue: GitHub.