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

  1. Guard the call: only invoke when file.IsRename() is true
  2. Refresh the files model before acting so rename detection (status -z with renames) is current
  3. 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

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


AI-assisted analysis of jesseduffield/lazygit@c477a2959b (2026-08-15). Data as JSON: /api/errors/f87fcde4649e0675. Report an issue: GitHub.