Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions pkg/gui/controllers/commits_files_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/constants"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
Expand Down Expand Up @@ -283,6 +284,12 @@ func (self *CommitFilesController) openCopyMenu() error {
}

func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error {
hasModifiedFiles := helpers.AnyTrackedFilesInPathExceptSubmodules(node.GetPath(),
self.c.Model().Files, self.c.Model().Submodules)
if hasModifiedFiles {
return errors.New(self.c.Tr.CannotCheckoutWithModifiedFilesErr)
}

self.c.LogAction(self.c.Tr.Actions.CheckoutFile)
_, to := self.context().GetFromAndToForDiff()
if err := self.c.Git().WorkingTree.CheckoutFile(to, node.GetPath()); err != nil {
Expand Down
18 changes: 18 additions & 0 deletions pkg/gui/controllers/helpers/working_tree_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"regexp"
"strings"

"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
Expand Down Expand Up @@ -71,6 +72,23 @@ func AnyTrackedFilesExceptSubmodules(files []*models.File, submoduleConfigs []*m
return lo.SomeBy(files, func(f *models.File) bool { return f.Tracked && !f.IsSubmodule(submoduleConfigs) })
}

func isContainedInPath(candidate string, path string) bool {
return (
// If the path is the repo root (appears as "/" in the UI), then all candidates are contained in it
path == "." ||
// Exact match; will only be true for files
candidate == path ||
// Match for files within a directory. We need to match the trailing slash to avoid
// matching files with longer names.
strings.HasPrefix(candidate, path+"/"))
}

func AnyTrackedFilesInPathExceptSubmodules(path string, files []*models.File, submoduleConfigs []*models.SubmoduleConfig) bool {
return lo.SomeBy(files, func(f *models.File) bool {
return f.Tracked && isContainedInPath(f.GetPath(), path) && !f.IsSubmodule(submoduleConfigs)
})
}

func (self *WorkingTreeHelper) IsWorkingTreeDirtyExceptSubmodules() bool {
return IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules)
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/i18n/english.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ type TranslationSet struct {
ViewItemFiles string
CommitFilesTitle string
CheckoutCommitFileTooltip string
CannotCheckoutWithModifiedFilesErr string
CanOnlyDiscardFromLocalCommits string
Remove string
DiscardOldFileChangeTooltip string
Expand Down Expand Up @@ -1525,6 +1526,7 @@ 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.",
CannotCheckoutWithModifiedFilesErr: "You have local modifications for the file(s) you are trying to check out. You need to stash or discard these first.",
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.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package commit

import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)

var CheckoutFileWithLocalModifications = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Checkout a file from a commit that has local modifications",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.CreateFileAndAdd("dir/file1.txt", "file1\n")
shell.CreateFileAndAdd("dir/file2.txt", "file2\n")
shell.Commit("one")
shell.UpdateFile("dir/file1.txt", "file1\nfile1 change\n")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Lines(
Contains("one").IsSelected(),
).
PressEnter()

t.Views().CommitFiles().
IsFocused().
Lines(
Equals("▼ dir").IsSelected(),
Equals(" A file1.txt"),
Equals(" A file2.txt"),
).
Press(keys.CommitFiles.CheckoutCommitFile)

t.ExpectPopup().Alert().Title(Equals("Error")).
Content(Contains("local modifications")).
Confirm()
},
})
1 change: 1 addition & 0 deletions pkg/integration/tests/test_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ var tests = []*components.IntegrationTest{
commit.Checkout,
commit.CheckoutFileFromCommit,
commit.CheckoutFileFromRangeSelectionOfCommits,
commit.CheckoutFileWithLocalModifications,
commit.Commit,
commit.CommitMultiline,
commit.CommitSkipHooks,
Expand Down