mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-04-23 12:18:51 +02:00
stateless undos and redos
This commit is contained in:
parent
094939451d
commit
c3aefdb98e
@ -144,13 +144,11 @@ func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type handleCheckoutRefOptions struct {
|
type handleCheckoutRefOptions struct {
|
||||||
OnDone func()
|
|
||||||
WaitingStatus string
|
WaitingStatus string
|
||||||
EnvVars []string
|
EnvVars []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleCheckoutRef(ref string, options handleCheckoutRefOptions) error {
|
func (gui *Gui) handleCheckoutRef(ref string, options handleCheckoutRefOptions) error {
|
||||||
onDone := options.OnDone
|
|
||||||
waitingStatus := options.WaitingStatus
|
waitingStatus := options.WaitingStatus
|
||||||
if waitingStatus == "" {
|
if waitingStatus == "" {
|
||||||
waitingStatus = gui.Tr.SLocalize("CheckingOutStatus")
|
waitingStatus = gui.Tr.SLocalize("CheckingOutStatus")
|
||||||
@ -172,9 +170,6 @@ func (gui *Gui) handleCheckoutRef(ref string, options handleCheckoutRefOptions)
|
|||||||
if err := gui.GitCommand.Checkout(ref, cmdOptions); err != nil {
|
if err := gui.GitCommand.Checkout(ref, cmdOptions); err != nil {
|
||||||
return gui.createErrorPanel(g, err.Error())
|
return gui.createErrorPanel(g, err.Error())
|
||||||
}
|
}
|
||||||
if onDone != nil {
|
|
||||||
onDone()
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkout successful so we select the new branch
|
// checkout successful so we select the new branch
|
||||||
gui.State.Panels.Branches.SelectedLine = 0
|
gui.State.Panels.Branches.SelectedLine = 0
|
||||||
@ -194,10 +189,6 @@ func (gui *Gui) handleCheckoutRef(ref string, options handleCheckoutRefOptions)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if onDone != nil {
|
|
||||||
onDone()
|
|
||||||
}
|
|
||||||
|
|
||||||
gui.State.Panels.Branches.SelectedLine = 0
|
gui.State.Panels.Branches.SelectedLine = 0
|
||||||
gui.State.Panels.Commits.SelectedLine = 0
|
gui.State.Panels.Commits.SelectedLine = 0
|
||||||
return gui.refreshSidePanels(gui.g)
|
return gui.refreshSidePanels(gui.g)
|
||||||
|
@ -214,24 +214,6 @@ type guiState struct {
|
|||||||
PrevMainWidth int
|
PrevMainWidth int
|
||||||
PrevMainHeight int
|
PrevMainHeight int
|
||||||
OldInformation string
|
OldInformation string
|
||||||
|
|
||||||
Undo UndoState
|
|
||||||
}
|
|
||||||
|
|
||||||
// we facilitate 'undo' actions via parsing the reflog and doing the reverse
|
|
||||||
// of the most recent entry. In order to to multiple undo's in a row we need to
|
|
||||||
// keep track of where we are in the reflog. We do that via a key which is the
|
|
||||||
// concatenation of the reflog's timestamp and message.
|
|
||||||
// We also store the index of that reflog entry so that if we end up with a
|
|
||||||
// different entry at that index we know the user must have done something
|
|
||||||
// themselves (e.g. checked out a branch) to cause new reflog entries to be created
|
|
||||||
// meaning we can reset the undo state.
|
|
||||||
type UndoState struct {
|
|
||||||
ReflogKey string
|
|
||||||
ReflogIdx int
|
|
||||||
// this is the index of the most recent reflog entry that the user initiated themselves
|
|
||||||
// (as opposed to being created by an undo or redo action)
|
|
||||||
UndoCount int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// for now the split view will always be on
|
// for now the split view will always be on
|
||||||
|
@ -102,52 +102,31 @@ func (gui *Gui) handleCreateReflogResetMenu(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return gui.createResetMenu(commit.Sha)
|
return gui.createResetMenu(commit.Sha)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
USER_ACTION = iota
|
||||||
|
UNDO
|
||||||
|
REDO
|
||||||
|
)
|
||||||
|
|
||||||
type reflogAction struct {
|
type reflogAction struct {
|
||||||
regexStr string
|
regexStr string
|
||||||
action func(match []string, commitSha string, onDone func()) (bool, error)
|
action func(match []string, commitSha string) error
|
||||||
}
|
kind int
|
||||||
|
|
||||||
func (gui *Gui) reflogKey(reflogCommit *commands.Commit) string {
|
|
||||||
return reflogCommit.Date + reflogCommit.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) idxOfUndoReflogKey(key string) int {
|
|
||||||
for i, reflogCommit := range gui.State.ReflogCommits {
|
|
||||||
if gui.reflogKey(reflogCommit) == key {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) setUndoReflogKey(key string) {
|
|
||||||
gui.State.Undo.ReflogKey = key
|
|
||||||
// adding one because this is called before we actually refresh the reflog on our end
|
|
||||||
// so the index will soon change.
|
|
||||||
gui.State.Undo.ReflogIdx = gui.idxOfUndoReflogKey(key) + 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) reflogUndo(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) reflogUndo(g *gocui.Gui, v *gocui.View) error {
|
||||||
reflogCommits := gui.State.ReflogCommits
|
reflogCommits := gui.State.ReflogCommits
|
||||||
|
|
||||||
// if the index of the previous reflog entry has changed, we need to start from the beginning, because it means there's been user input.
|
|
||||||
startIndex := gui.State.Undo.ReflogIdx
|
|
||||||
if gui.idxOfUndoReflogKey(gui.State.Undo.ReflogKey) != gui.State.Undo.ReflogIdx {
|
|
||||||
gui.State.Undo.UndoCount = 0
|
|
||||||
startIndex = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
envVars := []string{"GIT_REFLOG_ACTION=[lazygit undo]"}
|
envVars := []string{"GIT_REFLOG_ACTION=[lazygit undo]"}
|
||||||
|
|
||||||
|
// first step, work out what it is, second step, do an action if necessary
|
||||||
|
|
||||||
reflogActions := []reflogAction{
|
reflogActions := []reflogAction{
|
||||||
{
|
{
|
||||||
regexStr: `^checkout: moving from ([\S]+)`,
|
regexStr: `^checkout: moving from ([\S]+)`,
|
||||||
action: func(match []string, commitSha string, onDone func()) (bool, error) {
|
kind: USER_ACTION,
|
||||||
if len(match) <= 1 {
|
action: func(match []string, commitSha string) error {
|
||||||
return false, nil
|
return gui.handleCheckoutRef(match[1], handleCheckoutRefOptions{
|
||||||
}
|
|
||||||
return true, gui.handleCheckoutRef(match[1], handleCheckoutRefOptions{
|
|
||||||
OnDone: onDone,
|
|
||||||
WaitingStatus: gui.Tr.SLocalize("UndoingStatus"),
|
WaitingStatus: gui.Tr.SLocalize("UndoingStatus"),
|
||||||
EnvVars: envVars,
|
EnvVars: envVars,
|
||||||
},
|
},
|
||||||
@ -156,37 +135,45 @@ func (gui *Gui) reflogUndo(g *gocui.Gui, v *gocui.View) error {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
regexStr: `^commit|^rebase -i \(start\)|^reset: moving to|^pull`,
|
regexStr: `^commit|^rebase -i \(start\)|^reset: moving to|^pull`,
|
||||||
action: func(match []string, commitSha string, onDone func()) (bool, error) {
|
kind: USER_ACTION,
|
||||||
return true, gui.handleHardResetWithAutoStash(commitSha, handleHardResetWithAutoStashOptions{OnDone: onDone, EnvVars: envVars})
|
action: func(match []string, commitSha string) error {
|
||||||
|
return gui.handleHardResetWithAutoStash(commitSha, handleHardResetWithAutoStashOptions{EnvVars: envVars})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
regexStr: `^\[lazygit undo\]`,
|
||||||
|
kind: UNDO,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regexStr: `^\[lazygit redo\]`,
|
||||||
|
kind: REDO,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for offsetIdx, reflogCommit := range reflogCommits[startIndex:] {
|
counter := 0
|
||||||
i := offsetIdx + startIndex
|
for i, reflogCommit := range reflogCommits {
|
||||||
for _, action := range reflogActions {
|
for _, action := range reflogActions {
|
||||||
re := regexp.MustCompile(action.regexStr)
|
re := regexp.MustCompile(action.regexStr)
|
||||||
match := re.FindStringSubmatch(reflogCommit.Name)
|
match := re.FindStringSubmatch(reflogCommit.Name)
|
||||||
if len(match) == 0 {
|
if len(match) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
prevCommitSha := ""
|
|
||||||
if len(reflogCommits)-1 >= i+1 {
|
|
||||||
prevCommitSha = reflogCommits[i+1].Sha
|
|
||||||
}
|
|
||||||
|
|
||||||
nextKey := gui.reflogKey(reflogCommits[i+1])
|
switch action.kind {
|
||||||
onDone := func() {
|
case UNDO:
|
||||||
gui.setUndoReflogKey(nextKey)
|
counter++
|
||||||
gui.State.Undo.UndoCount++
|
case REDO:
|
||||||
|
counter--
|
||||||
|
case USER_ACTION:
|
||||||
|
counter--
|
||||||
|
if counter == -1 {
|
||||||
|
prevCommitSha := ""
|
||||||
|
if len(reflogCommits)-1 >= i+1 {
|
||||||
|
prevCommitSha = reflogCommits[i+1].Sha
|
||||||
|
}
|
||||||
|
return action.action(match, prevCommitSha)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isMatchingAction, err := action.action(match, prevCommitSha, onDone)
|
|
||||||
if !isMatchingAction {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,23 +183,13 @@ func (gui *Gui) reflogUndo(g *gocui.Gui, v *gocui.View) error {
|
|||||||
func (gui *Gui) reflogRedo(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) reflogRedo(g *gocui.Gui, v *gocui.View) error {
|
||||||
reflogCommits := gui.State.ReflogCommits
|
reflogCommits := gui.State.ReflogCommits
|
||||||
|
|
||||||
// if the index of the previous reflog entry has changed there is nothing to redo because there's been a user action
|
|
||||||
startIndex := gui.State.Undo.ReflogIdx
|
|
||||||
if gui.idxOfUndoReflogKey(gui.State.Undo.ReflogKey) != gui.State.Undo.ReflogIdx || startIndex == 0 || gui.State.Undo.UndoCount == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
envVars := []string{"GIT_REFLOG_ACTION=[lazygit redo]"}
|
envVars := []string{"GIT_REFLOG_ACTION=[lazygit redo]"}
|
||||||
|
|
||||||
reflogActions := []reflogAction{
|
reflogActions := []reflogAction{
|
||||||
{
|
{
|
||||||
regexStr: `^checkout: moving from [\S]+ to ([\S]+)`,
|
regexStr: `^checkout: moving from [\S]+ to ([\S]+)`,
|
||||||
action: func(match []string, commitSha string, onDone func()) (bool, error) {
|
action: func(match []string, commitSha string) error {
|
||||||
if len(match) <= 1 {
|
return gui.handleCheckoutRef(match[1], handleCheckoutRefOptions{
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return true, gui.handleCheckoutRef(match[1], handleCheckoutRefOptions{
|
|
||||||
OnDone: onDone,
|
|
||||||
WaitingStatus: gui.Tr.SLocalize("RedoingStatus"),
|
WaitingStatus: gui.Tr.SLocalize("RedoingStatus"),
|
||||||
EnvVars: envVars,
|
EnvVars: envVars,
|
||||||
},
|
},
|
||||||
@ -221,15 +198,22 @@ func (gui *Gui) reflogRedo(g *gocui.Gui, v *gocui.View) error {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
regexStr: `^commit|^rebase -i \(start\)|^reset: moving to|^pull`,
|
regexStr: `^commit|^rebase -i \(start\)|^reset: moving to|^pull`,
|
||||||
action: func(match []string, commitSha string, onDone func()) (bool, error) {
|
action: func(match []string, commitSha string) error {
|
||||||
return true, gui.handleHardResetWithAutoStash(commitSha, handleHardResetWithAutoStashOptions{OnDone: onDone, EnvVars: envVars})
|
return gui.handleHardResetWithAutoStash(commitSha, handleHardResetWithAutoStashOptions{EnvVars: envVars})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
regexStr: `^\[lazygit undo\]`,
|
||||||
|
kind: UNDO,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regexStr: `^\[lazygit redo\]`,
|
||||||
|
kind: REDO,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := startIndex - 1; i > 0; i++ {
|
counter := 0
|
||||||
reflogCommit := reflogCommits[i]
|
for _, reflogCommit := range reflogCommits {
|
||||||
|
|
||||||
for _, action := range reflogActions {
|
for _, action := range reflogActions {
|
||||||
re := regexp.MustCompile(action.regexStr)
|
re := regexp.MustCompile(action.regexStr)
|
||||||
match := re.FindStringSubmatch(reflogCommit.Name)
|
match := re.FindStringSubmatch(reflogCommit.Name)
|
||||||
@ -237,18 +221,19 @@ func (gui *Gui) reflogRedo(g *gocui.Gui, v *gocui.View) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
prevKey := gui.reflogKey(reflogCommits[i-1])
|
switch action.kind {
|
||||||
onDone := func() {
|
case UNDO:
|
||||||
gui.setUndoReflogKey(prevKey)
|
counter++
|
||||||
gui.State.Undo.UndoCount--
|
case REDO:
|
||||||
|
counter--
|
||||||
|
case USER_ACTION:
|
||||||
|
counter--
|
||||||
|
if counter == 0 {
|
||||||
|
return action.action(match, reflogCommit.Sha)
|
||||||
|
} else if counter < 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isMatchingAction, err := action.action(match, reflogCommit.Sha, onDone)
|
|
||||||
if !isMatchingAction {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,7 +241,6 @@ func (gui *Gui) reflogRedo(g *gocui.Gui, v *gocui.View) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type handleHardResetWithAutoStashOptions struct {
|
type handleHardResetWithAutoStashOptions struct {
|
||||||
OnDone func()
|
|
||||||
EnvVars []string
|
EnvVars []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,7 +259,6 @@ func (gui *Gui) handleHardResetWithAutoStash(commitSha string, options handleHar
|
|||||||
if err := gui.resetToRef(commitSha, "hard", commands.RunCommandOptions{EnvVars: options.EnvVars}); err != nil {
|
if err := gui.resetToRef(commitSha, "hard", commands.RunCommandOptions{EnvVars: options.EnvVars}); err != nil {
|
||||||
return gui.createErrorPanel(gui.g, err.Error())
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
}
|
}
|
||||||
options.OnDone()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user