summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pkg/gui/reflog_panel.go160
-rw-r--r--pkg/gui/undoing.go176
2 files changed, 176 insertions, 160 deletions
diff --git a/pkg/gui/reflog_panel.go b/pkg/gui/reflog_panel.go
index 691ff34bb..fd26a67c8 100644
--- a/pkg/gui/reflog_panel.go
+++ b/pkg/gui/reflog_panel.go
@@ -1,8 +1,6 @@
package gui
import (
- "regexp"
-
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
@@ -101,161 +99,3 @@ func (gui *Gui) handleCreateReflogResetMenu(g *gocui.Gui, v *gocui.View) error {
return gui.createResetMenu(commit.Sha)
}
-
-const (
- USER_ACTION = iota
- UNDO
- REDO
-)
-
-type reflogAction struct {
- regexStr string
- action func(match []string, commitSha string, waitingStatus string, envVars []string, isUndo bool) error
- kind int
-}
-
-func (gui *Gui) reflogActions() []reflogAction {
- return []reflogAction{
- {
- regexStr: `^checkout: moving from ([\S]+) to ([\S]+)`,
- kind: USER_ACTION,
- action: func(match []string, commitSha string, waitingStatus string, envVars []string, isUndo bool) error {
- branchName := match[2]
- if isUndo {
- branchName = match[1]
- }
- return gui.handleCheckoutRef(branchName, handleCheckoutRefOptions{
- WaitingStatus: waitingStatus,
- EnvVars: envVars,
- },
- )
- },
- },
- {
- regexStr: `^commit|^rebase -i \(start\)|^reset: moving to|^pull`,
- kind: USER_ACTION,
- action: func(match []string, commitSha string, waitingStatus string, envVars []string, isUndo bool) error {
- return gui.handleHardResetWithAutoStash(commitSha, handleHardResetWithAutoStashOptions{EnvVars: envVars, WaitingStatus: waitingStatus})
- },
- },
- {
- regexStr: `^\[lazygit undo\]`,
- kind: UNDO,
- },
- {
- regexStr: `^\[lazygit redo\]`,
- kind: REDO,
- },
- }
-}
-
-func (gui *Gui) reflogUndo(g *gocui.Gui, v *gocui.View) error {
- return gui.iterateUserActions(func(match []string, reflogCommits []*commands.Commit, reflogIdx int, action reflogAction, counter int) (bool, error) {
- if counter == -1 {
- prevCommitSha := ""
- if len(reflogCommits)-1 >= reflogIdx+1 {
- prevCommitSha = reflogCommits[reflogIdx+1].Sha
- }
- return true, action.action(match, prevCommitSha, gui.Tr.SLocalize("UndoingStatus"), []string{"GIT_REFLOG_ACTION=[lazygit undo]"}, true)
- } else {
- return false, nil
- }
- })
-}
-
-func (gui *Gui) reflogRedo(g *gocui.Gui, v *gocui.View) error {
- return gui.iterateUserActions(func(match []string, reflogCommits []*commands.Commit, reflogIdx int, action reflogAction, counter int) (bool, error) {
- if counter == 0 {
- return true, action.action(match, reflogCommits[reflogIdx].Sha, gui.Tr.SLocalize("RedoingStatus"), []string{"GIT_REFLOG_ACTION=[lazygit redo]"}, false)
- } else if counter < 0 {
- return true, nil
- } else {
- return false, nil
- }
- })
-}
-
-func (gui *Gui) iterateUserActions(onUserAction func(match []string, reflogCommits []*commands.Commit, reflogIdx int, action reflogAction, counter int) (bool, error)) error {
- reflogCommits := gui.State.ReflogCommits
-
- counter := 0
- for i, reflogCommit := range reflogCommits {
- for _, action := range gui.reflogActions() {
- re := regexp.MustCompile(action.regexStr)
- match := re.FindStringSubmatch(reflogCommit.Name)
- if len(match) == 0 {
- continue
- }
-
- switch action.kind {
- case UNDO:
- counter++
- case REDO:
- counter--
- case USER_ACTION:
- counter--
- shouldReturn, err := onUserAction(match, reflogCommits, i, action, counter)
- if err != nil {
- return err
- }
- if shouldReturn {
- return nil
- }
- }
- }
- }
- return nil
-}
-
-type handleHardResetWithAutoStashOptions struct {
- WaitingStatus string
- EnvVars []string
-}
-
-// only to be used in the undo flow for now
-func (gui *Gui) handleHardResetWithAutoStash(commitSha string, options handleHardResetWithAutoStashOptions) error {
- // if we have any modified tracked files we need to ask the user if they want us to stash for them
- dirtyWorkingTree := false
- for _, file := range gui.State.Files {
- if file.Tracked {
- dirtyWorkingTree = true
- break
- }
- }
-
- reset := func() error {
- if err := gui.resetToRef(commitSha, "hard", commands.RunCommandOptions{EnvVars: options.EnvVars}); err != nil {
- return gui.createErrorPanel(gui.g, err.Error())
- }
- return nil
- }
-
- if dirtyWorkingTree {
- // offer to autostash changes
- return gui.createConfirmationPanel(gui.g, gui.getBranchesView(), true, gui.Tr.SLocalize("AutoStashTitle"), gui.Tr.SLocalize("AutoStashPrompt"), func(g *gocui.Gui, v *gocui.View) error {
- return gui.WithWaitingStatus(options.WaitingStatus, func() error {
- if err := gui.GitCommand.StashSave(gui.Tr.SLocalize("StashPrefix") + commitSha); err != nil {
- return gui.createErrorPanel(g, err.Error())
- }
- if err := reset(); err != nil {
- return err
- }
-
- if err := gui.GitCommand.StashDo(0, "pop"); err != nil {
- if err := gui.refreshSidePanels(g); err != nil {
- return err
- }
- return gui.createErrorPanel(g, err.Error())
- }
- return gui.refreshSidePanels(g)
- })
- }, nil)
- }
-
- return gui.WithWaitingStatus(options.WaitingStatus, func() error {
- if err := reset(); err != nil {
- return err
- }
- return gui.refreshSidePanels(gui.g)
- })
-}
diff --git a/pkg/gui/undoing.go b/pkg/gui/undoing.go
new file mode 100644
index 000000000..f050eb5c1
--- /dev/null
+++ b/pkg/gui/undoing.go
@@ -0,0 +1,176 @@
+package gui
+
+import (
+ "regexp"
+
+ "github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/commands"
+)
+
+// Quick summary of how this all works:
+// when you want to undo or redo, we start from the top of the reflog and work
+// down until we've reached the last user-initiated reflog entry that hasn't already been undone
+// we then do the reverse of what that reflog describes.
+// When we do this, we create a new reflog entry, and tag it as either an undo or redo
+// Then, next time we want to undo, we'll use those entries to know which user-initiated
+// actions we can skip. E.g. if I do do three things, A, B, and C, and hit undo twice,
+// the reflog will read UUCBA, and when I read the first two undos, I know to skip the following
+// two user actions, meaning we end up undoing reflog entry C. Redoing works in a similar way.
+
+const (
+ USER_ACTION = iota
+ UNDO
+ REDO
+)
+
+type reflogAction struct {
+ regexStr string
+ action func(match []string, commitSha string, waitingStatus string, envVars []string, isUndo bool) error
+ kind int
+}
+
+func (gui *Gui) reflogActions() []reflogAction {
+ return []reflogAction{
+ {
+ regexStr: `^checkout: moving from ([\S]+) to ([\S]+)`,
+ kind: USER_ACTION,
+ action: func(match []string, commitSha string, waitingStatus string, envVars []string, isUndo bool) error {
+ branchName := match[2]
+ if isUndo {
+ branchName = match[1]
+ }
+ return gui.handleCheckoutRef(branchName, handleCheckoutRefOptions{
+ WaitingStatus: waitingStatus,
+ EnvVars: envVars,
+ },
+ )
+ },
+ },
+ {
+ regexStr: `^commit|^rebase -i \(start\)|^reset: moving to|^pull`,
+ kind: USER_ACTION,
+ action: func(match []string, commitSha string, waitingStatus string, envVars []string, isUndo bool) error {
+ return gui.handleHardResetWithAutoStash(commitSha, handleHardResetWithAutoStashOptions{EnvVars: envVars, WaitingStatus: waitingStatus})
+ },
+ },
+ {
+ regexStr: `^\[lazygit undo\]`,
+ kind: UNDO,
+ },
+ {
+ regexStr: `^\[lazygit redo\]`,
+ kind: REDO,
+ },
+ }
+}
+
+func (gui *Gui) reflogUndo(g *gocui.Gui, v *gocui.View) error {
+ return gui.iterateUserActions(func(match []string, reflogCommits []*commands.Commit, reflogIdx int, action reflogAction, counter int) (bool, error) {
+ if counter == -1 {
+ prevCommitSha := ""
+ if len(reflogCommits)-1 >= reflogIdx+1 {
+ prevCommitSha = reflogCommits[reflogIdx+1].Sha
+ }
+ return true, action.action(match, prevCommitSha, gui.Tr.SLocalize("UndoingStatus"), []string{"GIT_REFLOG_ACTION=[lazygit undo]"}, true)
+ } else {
+ return false, nil
+ }
+ })
+}
+
+func (gui *Gui) reflogRedo(g *gocui.Gui, v *gocui.View) error {
+ return gui.iterateUserActions(func(match []string, reflogCommits []*commands.Commit, reflogIdx int, action reflogAction, counter int) (bool, error) {
+ if counter == 0 {
+ return true, action.action(match, reflogCommits[reflogIdx].Sha, gui.Tr.SLocalize("RedoingStatus"), []string{"GIT_REFLOG_ACTION=[lazygit redo]"}, false)
+ } else if counter < 0 {
+ return true, nil
+ } else {
+ return false, nil
+ }
+ })
+}
+
+func (gui *Gui) iterateUserActions(onUserAction func(match []string, reflogCommits []*commands.Commit, reflogIdx int, action reflogAction, counter int) (bool, error)) error {
+ reflogCommits := gui.State.ReflogCommits
+
+ counter := 0
+ for i, reflogCommit := range reflogCommits {
+ for _, action := range gui.reflogActions() {
+ re := regexp.MustCompile(action.regexStr)
+ match := re.FindStringSubmatch(reflogCommit.Name)
+ if len(match) == 0 {
+ continue
+ }
+
+ switch action.kind {
+ case UNDO:
+ counter++
+ case REDO:
+ counter--
+ case USER_ACTION:
+ counter--
+ shouldReturn, err := onUserAction(match, reflogCommits, i, action, counter)
+ if err != nil {
+ return err
+ }
+ if shouldReturn {
+ return nil
+ }
+ }
+ }
+ }
+ return nil
+}
+
+type handleHardResetWithAutoStashOptions struct {
+ WaitingStatus string
+ EnvVars []string
+}
+
+// only to be used in the undo flow for now
+func (gui *Gui) handleHardResetWithAutoStash(commitSha string, options handleHardResetWithAutoStashOptions) error {
+ // if we have any modified tracked files we need to ask the user if they want us to stash for them
+ dirtyWorkingTree := false
+ for _, file := range gui.State.Files {
+ if file.Tracked {
+ dirtyWorkingTree = true
+ break
+ }
+ }
+
+ reset := func() error {
+ if err := gui.resetToRef(commitSha, "hard", commands.RunCommandOptions{EnvVars: options.EnvVars}); err != nil {
+ return gui.createErrorPanel(gui.g, err.Error())
+ }
+ return nil
+ }
+
+ if dirtyWorkingTree {
+ // offer to autostash changes
+ return gui.createConfirmationPanel(gui.g, gui.getBranchesView(), true, gui.Tr.SLocalize("AutoStashTitle"), gui.Tr.SLocalize("AutoStashPrompt"), func(g *gocui.Gui, v *gocui.View) error {
+ return gui.WithWaitingStatus(options.WaitingStatus, func() error {
+ if err := gui.GitCommand.StashSave(gui.Tr.SLocalize("StashPrefix") + commitSha); err != nil {
+ return gui.createErrorPanel(g, err.Error())
+ }
+ if err := reset(); err != nil {
+ return err
+ }
+
+ if err := gui.GitCommand.StashDo(0, "pop"); err != nil {
+ if err := gui.refreshSidePanels(g); err != nil {
+ return err
+ }
+ return gui.createErrorPanel(g, err.Error())
+ }
+ return gui.refreshSidePanels(g)
+ })
+ }, nil)
+ }
+
+ return gui.WithWaitingStatus(options.WaitingStatus, func() error {
+ if err := reset(); err != nil {
+ return err
+ }
+ return gui.refreshSidePanels(gui.g)
+ })
+}