Improve undo action to restore files upon undoing a commit

This commit is contained in:
Gabriel Lanata 2025-01-12 12:33:21 -08:00 committed by Jesse Duffield
parent 43106b6c7f
commit 4065175a58
4 changed files with 144 additions and 26 deletions

View file

@ -88,7 +88,20 @@ func (self *UndoController) reflogUndo() error {
}
switch action.kind {
case COMMIT, REBASE:
case COMMIT:
self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.Actions.Undo,
Prompt: fmt.Sprintf(self.c.Tr.SoftResetPrompt, action.from),
HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.Undo)
return self.c.WithWaitingStatus(undoingStatus, func(gocui.Task) error {
return self.c.Helpers().Refs.ResetToRef(action.from, "soft", undoEnvVars)
})
},
})
return true, nil
case REBASE:
self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.Actions.Undo,
Prompt: fmt.Sprintf(self.c.Tr.HardResetAutostashPrompt, action.from),
@ -105,7 +118,7 @@ func (self *UndoController) reflogUndo() error {
case CHECKOUT:
self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.Actions.Undo,
Prompt: fmt.Sprintf(self.c.Tr.CheckoutPrompt, action.from),
Prompt: fmt.Sprintf(self.c.Tr.CheckoutAutostashPrompt, action.from),
HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.Undo)
return self.c.Helpers().Refs.CheckoutRef(action.from, types.CheckoutRefOptions{
@ -159,7 +172,7 @@ func (self *UndoController) reflogRedo() error {
case CHECKOUT:
self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.Actions.Redo,
Prompt: fmt.Sprintf(self.c.Tr.CheckoutPrompt, action.to),
Prompt: fmt.Sprintf(self.c.Tr.CheckoutAutostashPrompt, action.to),
HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.Redo)
return self.c.Helpers().Refs.CheckoutRef(action.to, types.CheckoutRefOptions{
@ -244,31 +257,23 @@ func (self *UndoController) hardResetWithAutoStash(commitHash string, options ha
return self.c.Helpers().Refs.ResetToRef(commitHash, "hard", options.EnvVars)
}
// if we have any modified tracked files we need to ask the user if they want us to stash for them
// if we have any modified tracked files we need to auto-stash
dirtyWorkingTree := self.c.Helpers().WorkingTree.IsWorkingTreeDirty()
if dirtyWorkingTree {
// offer to autostash changes
self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.AutoStashTitle,
Prompt: self.c.Tr.AutoStashPrompt,
HandleConfirm: func() error {
return self.c.WithWaitingStatus(options.WaitingStatus, func(gocui.Task) error {
if err := self.c.Git().Stash.Push(self.c.Tr.StashPrefix + commitHash); err != nil {
return err
}
if err := reset(); err != nil {
return err
}
return self.c.WithWaitingStatus(options.WaitingStatus, func(gocui.Task) error {
if err := self.c.Git().Stash.Push(self.c.Tr.StashPrefix + commitHash); err != nil {
return err
}
if err := reset(); err != nil {
return err
}
err := self.c.Git().Stash.Pop(0)
if err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{})
})
},
err := self.c.Git().Stash.Pop(0)
if err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{})
})
return nil
}
return self.c.WithWaitingStatus(options.WaitingStatus, func(gocui.Task) error {

View file

@ -732,8 +732,9 @@ type TranslationSet struct {
ConfirmRevertCommit string
RewordInEditorTitle string
RewordInEditorPrompt string
CheckoutPrompt string
CheckoutAutostashPrompt string
HardResetAutostashPrompt string
SoftResetPrompt string
UpstreamGone string
NukeDescription string
DiscardStagedChangesDescription string
@ -1745,7 +1746,8 @@ func EnglishTranslationSet() *TranslationSet {
RewordInEditorTitle: "Reword in editor",
RewordInEditorPrompt: "Are you sure you want to reword this commit in your editor?",
HardResetAutostashPrompt: "Are you sure you want to hard reset to '%s'? An auto-stash will be performed if necessary.",
CheckoutPrompt: "Are you sure you want to checkout '%s'?",
SoftResetPrompt: "Are you sure you want to soft reset to '%s'?",
CheckoutAutostashPrompt: "Are you sure you want to checkout '%s'? An auto-stash will be performed if necessary.",
UpstreamGone: "(upstream gone)",
NukeDescription: "If you want to make all the changes in the worktree go away, this is the way to do it. If there are dirty submodule changes this will stash those changes in the submodule(s).",
DiscardStagedChangesDescription: "This will create a new stash entry containing only staged files and then drop it, so that the working tree is left with only unstaged changes",

View file

@ -367,6 +367,7 @@ var tests = []*components.IntegrationTest{
ui.SwitchTabFromMenu,
ui.SwitchTabWithPanelJumpKeys,
undo.UndoCheckoutAndDrop,
undo.UndoCommit,
undo.UndoDrop,
worktree.AddFromBranch,
worktree.AddFromBranchDetached,

View file

@ -0,0 +1,110 @@
package undo
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var UndoCommit = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Undo/redo a commit",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.CreateFileAndAdd("other-file", "other-file-1")
shell.Commit("one")
shell.CreateFileAndAdd("file", "file-1")
shell.Commit("two")
shell.UpdateFile("other-file", "other-file-2")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
confirmUndo := func() {
t.ExpectPopup().Confirmation().
Title(Equals("Undo")).
Content(MatchesRegexp(`Are you sure you want to soft reset to '.*'\?`)).
Confirm()
}
confirmRedo := func() {
t.ExpectPopup().Confirmation().
Title(Equals("Redo")).
Content(MatchesRegexp(`Are you sure you want to hard reset to '.*'\? An auto-stash will be performed if necessary\.`)).
Confirm()
}
confirmDiscardFile := func() {
t.ExpectPopup().Menu().
Title(Equals("Discard changes")).
Select(Contains("Discard all changes")).
Confirm()
}
t.Views().Files().
Lines(
Contains(" M other-file"),
)
t.Views().Commits().Focus().
Lines(
Contains("two").IsSelected(),
Contains("one"),
).
Press(keys.Universal.Undo).
Tap(confirmUndo).
Lines(
Contains("one").IsSelected(),
)
t.Views().Files().
Lines(
Contains("A file"),
Contains(" M other-file"),
)
t.Views().Commits().Focus().
Press(keys.Universal.Redo).
Tap(confirmRedo).
Lines(
Contains("two").IsSelected(),
Contains("one"),
)
t.Views().Files().
Lines(
Contains(" M other-file"),
)
// Undo again, this time discarding the original change before redoing again
t.Views().Commits().Focus().
Press(keys.Universal.Undo).
Tap(confirmUndo).
Lines(
Contains("one").IsSelected(),
)
t.Views().Files().Focus().
Lines(
Contains("A file"),
Contains(" M other-file").IsSelected(),
).
Press(keys.Universal.PrevItem).
Press(keys.Universal.Remove).
Tap(confirmDiscardFile).
Lines(
Contains(" M other-file"),
).
Press(keys.Universal.Redo).
Tap(confirmRedo)
t.Views().Commits().
Lines(
Contains("two"),
Contains("one"),
)
t.Views().Files().
Lines(
Contains(" M other-file"),
)
},
})