diff --git a/pkg/gui/controllers/commits_files_controller.go b/pkg/gui/controllers/commits_files_controller.go index 45bfa5f0b9c..2dd4234a40f 100644 --- a/pkg/gui/controllers/commits_files_controller.go +++ b/pkg/gui/controllers/commits_files_controller.go @@ -283,14 +283,25 @@ func (self *CommitFilesController) openCopyMenu() error { } func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error { - self.c.LogAction(self.c.Tr.Actions.CheckoutFile) - _, to := self.context().GetFromAndToForDiff() - if err := self.c.Git().WorkingTree.CheckoutFile(to, node.GetPath()); err != nil { - return err - } + // Check if the file has uncommitted local changes + hasLocalChanges := lo.SomeBy(self.c.Model().Files, func(file *models.File) bool { + return file.GetPath() == node.GetPath() && (file.HasStagedChanges || file.HasUnstagedChanges) + }) - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return nil + return self.c.ConfirmIf(hasLocalChanges, types.ConfirmOpts{ + Title: self.c.Tr.CheckoutFileFromCommitTitle, + Prompt: self.c.Tr.CheckoutFileFromCommitPrompt, + HandleConfirm: func() error { + self.c.LogAction(self.c.Tr.Actions.CheckoutFile) + _, to := self.context().GetFromAndToForDiff() + if err := self.c.Git().WorkingTree.CheckoutFile(to, node.GetPath()); err != nil { + return err + } + + self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) + return nil + }, + }) } func (self *CommitFilesController) discard(selectedNodes []*filetree.CommitFileNode) error { diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 75391a2c1f2..a5446629cc0 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -425,6 +425,8 @@ type TranslationSet struct { ViewItemFiles string CommitFilesTitle string CheckoutCommitFileTooltip string + CheckoutFileFromCommitTitle string + CheckoutFileFromCommitPrompt string CanOnlyDiscardFromLocalCommits string Remove string DiscardOldFileChangeTooltip string @@ -1525,6 +1527,8 @@ func EnglishTranslationSet() *TranslationSet { ViewItemFiles: "View files", CommitFilesTitle: "Commit files", CheckoutCommitFileTooltip: "Checkout file. This replaces the file in your working tree with the version from the selected commit.", + CheckoutFileFromCommitTitle: "Checkout file from commit", + CheckoutFileFromCommitPrompt: "Are you sure you want to checkout this file? Your uncommitted changes will be lost.", CanOnlyDiscardFromLocalCommits: "Changes can only be discarded from local commits", Remove: "Remove", DiscardOldFileChangeTooltip: "Discard this commit's changes to this file. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes this file.", diff --git a/pkg/integration/tests/commit/checkout_file_from_commit_with_local_changes.go b/pkg/integration/tests/commit/checkout_file_from_commit_with_local_changes.go new file mode 100644 index 00000000000..672367d0f71 --- /dev/null +++ b/pkg/integration/tests/commit/checkout_file_from_commit_with_local_changes.go @@ -0,0 +1,55 @@ +package commit + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var CheckoutFileFromCommitWithLocalChanges = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Checkout a file from a commit when there are local changes, showing a confirmation", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell.CreateFileAndAdd("file.txt", "one\n") + shell.Commit("one") + shell.CreateFileAndAdd("file.txt", "two\n") + shell.Commit("two") + shell.CreateFileAndAdd("file.txt", "three\n") + shell.Commit("three") + // Create local uncommitted changes + shell.UpdateFile("file.txt", "local changes\n") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("three").IsSelected(), + Contains("two"), + Contains("one"), + ). + NavigateToLine(Contains("two")). + PressEnter() + + t.Views().CommitFiles(). + IsFocused(). + Lines( + Equals("M file.txt"), + ). + Press(keys.CommitFiles.CheckoutCommitFile) + + // Should show confirmation dialog + t.ExpectPopup().Confirmation(). + Title(Equals("Checkout file from commit")). + Content(Contains("Are you sure you want to checkout this file? Your uncommitted changes will be lost.")). + Confirm() + + // After confirmation, file should be checked out + t.Views().Files(). + Lines( + Equals("M file.txt"), + ) + + t.FileSystem().FileContent("file.txt", Equals("two\n")) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index f19d5aef333..30bf78a14fd 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -106,6 +106,7 @@ var tests = []*components.IntegrationTest{ commit.AutoWrapMessage, commit.Checkout, commit.CheckoutFileFromCommit, + commit.CheckoutFileFromCommitWithLocalChanges, commit.CheckoutFileFromRangeSelectionOfCommits, commit.Commit, commit.CommitMultiline,