1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-07-15 01:34:26 +02:00

Stage affected unstaged files when applying or reverting a patch

Unlike moving a patch to the index, applying or reverting a patch didn't
auto-stash, which means that applying a patch when there's a modified (but
unstaged) file in the working tree would error out with the message "error:
file1: does not match index", regardless of whether those modifications conflict
with the patch or not.

To fix this, we *could* add auto-stashing like we do for the "move patch to
index" command. However, in this case we rather simply stage the affected files
(after asking for confirmation). This has a few advantages:

- it only changes the staging state of those files that are contained in the
patch (whereas auto-stashing always changes all files to unstaged)
- it doesn't unnecessarily show a confirmation if none of the modified files are
affected by the patch
- if the patch conflicts with the modified files, the conflicts were "backwards"
("ours" was the patch, "theirs" the modified file); it is more logical if "ours"
is the current state of the file, and "theirs" is the patch.

It's a little unfortunate that the behavior isn't exactly the same as for "move
patch to index", but for that one we do need the auto-stash because of the
rebase that runs behind the scenes.
This commit is contained in:
Stefan Haller
2025-06-30 17:59:36 +02:00
parent c440a208a6
commit 3df894ec92
4 changed files with 91 additions and 12 deletions

View File

@ -4,10 +4,13 @@ import (
"errors"
"fmt"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type CustomPatchOptionsMenuAction struct {
@ -267,16 +270,42 @@ func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommitBefore() e
func (self *CustomPatchOptionsMenuAction) handleApplyPatch(reverse bool) error {
self.returnFocusFromPatchExplorerIfNecessary()
action := self.c.Tr.Actions.ApplyPatch
if reverse {
action = "Apply patch in reverse"
affectedUnstagedFiles := self.getAffectedUnstagedFiles()
apply := func() error {
action := self.c.Tr.Actions.ApplyPatch
if reverse {
action = "Apply patch in reverse"
}
self.c.LogAction(action)
if len(affectedUnstagedFiles) > 0 {
if err := self.c.Git().WorkingTree.StageFiles(affectedUnstagedFiles, nil); err != nil {
return err
}
}
if err := self.c.Git().Patch.ApplyCustomPatch(reverse, true); err != nil {
return err
}
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
}
self.c.LogAction(action)
if err := self.c.Git().Patch.ApplyCustomPatch(reverse, true); err != nil {
return err
if len(affectedUnstagedFiles) > 0 {
self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.MustStageFilesAffectedByPatchTitle,
Prompt: self.c.Tr.MustStageFilesAffectedByPatchWarning,
HandleConfirm: func() error {
return apply()
},
})
return nil
}
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
return apply()
}
func (self *CustomPatchOptionsMenuAction) copyPatchToClipboard() error {
@ -291,3 +320,17 @@ func (self *CustomPatchOptionsMenuAction) copyPatchToClipboard() error {
return nil
}
// Returns a list of files that have unstaged changes and are contained in the patch.
func (self *CustomPatchOptionsMenuAction) getAffectedUnstagedFiles() []string {
unstagedFiles := set.NewFromSlice(lo.FilterMap(self.c.Model().Files, func(f *models.File, _ int) (string, bool) {
if f.GetHasUnstagedChanges() {
return f.GetPath(), true
}
return "", false
}))
return lo.Filter(self.c.Git().Patch.PatchBuilder.AllFilesInPatch(), func(patchFile string, _ int) bool {
return unstagedFiles.Includes(patchFile)
})
}