summaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2020-03-21 11:56:34 +1100
committerJesse Duffield <jessedduffield@gmail.com>2020-03-25 09:39:04 +1100
commitf80d15062b4296c452645b167e76c1a5a13e52fc (patch)
tree7b3d14c768c69e6a4095483c9d9679dc2f4103bc /pkg
parentb1b0219f04ab0e17dd0ce0f5469dee7daecf2717 (diff)
use reflog undo history pointer
Diffstat (limited to 'pkg')
-rw-r--r--pkg/commands/git.go9
-rw-r--r--pkg/gui/branches_panel.go13
-rw-r--r--pkg/gui/commits_panel.go2
-rw-r--r--pkg/gui/gui.go15
-rw-r--r--pkg/gui/reflog_panel.go67
-rw-r--r--pkg/gui/remote_branches_panel.go2
-rw-r--r--pkg/gui/tags_panel.go2
-rw-r--r--pkg/gui/tasks_adapter.go1
8 files changed, 84 insertions, 27 deletions
diff --git a/pkg/commands/git.go b/pkg/commands/git.go
index 6fabfe273..222d53af0 100644
--- a/pkg/commands/git.go
+++ b/pkg/commands/git.go
@@ -1106,15 +1106,15 @@ func (c *GitCommand) FetchRemote(remoteName string) error {
}
func (c *GitCommand) GetReflogCommits() ([]*Commit, error) {
- output, err := c.OSCommand.RunCommandWithOutput("git reflog --abbrev=20")
+ output, err := c.OSCommand.RunCommandWithOutput("git reflog --abbrev=20 --date=iso")
if err != nil {
// assume error means we have no reflog
return []*Commit{}, nil
}
lines := strings.Split(strings.TrimSpace(output), "\n")
- commits := make([]*Commit, 0)
- re := regexp.MustCompile(`(\w+).*HEAD@\{\d+\}: (.*)`)
+ commits := make([]*Commit, 0, len(lines))
+ re := regexp.MustCompile(`(\w+).*HEAD@\{([^\}]+)\}: (.*)`)
for _, line := range lines {
match := re.FindStringSubmatch(line)
if len(match) <= 1 {
@@ -1123,7 +1123,8 @@ func (c *GitCommand) GetReflogCommits() ([]*Commit, error) {
commit := &Commit{
Sha: match[1],
- Name: match[2],
+ Name: match[3],
+ Date: match[2],
Status: "reflog",
}
diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go
index b2e9b1525..49a0f0963 100644
--- a/pkg/gui/branches_panel.go
+++ b/pkg/gui/branches_panel.go
@@ -106,7 +106,7 @@ func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error {
return gui.createErrorPanel(g, gui.Tr.SLocalize("AlreadyCheckedOutBranch"))
}
branch := gui.getSelectedBranch()
- return gui.handleCheckoutRef(branch.Name)
+ return gui.handleCheckoutRef(branch.Name, nil)
}
func (gui *Gui) handleCreatePullRequestPress(g *gocui.Gui, v *gocui.View) error {
@@ -143,7 +143,7 @@ func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error {
}, nil)
}
-func (gui *Gui) handleCheckoutRef(ref string) error {
+func (gui *Gui) handleCheckoutRef(ref string, onDone func()) error {
if err := gui.GitCommand.Checkout(ref, false); err != nil {
// note, this will only work for english-language git commands. If we force git to use english, and the error isn't this one, then the user will receive an english command they may not understand. I'm not sure what the best solution to this is. Running the command once in english and a second time in the native language is one option
@@ -156,6 +156,9 @@ func (gui *Gui) handleCheckoutRef(ref string) error {
if err := gui.GitCommand.Checkout(ref, false); err != nil {
return gui.createErrorPanel(g, err.Error())
}
+ if onDone != nil {
+ onDone()
+ }
// checkout successful so we select the new branch
gui.State.Panels.Branches.SelectedLine = 0
@@ -175,6 +178,10 @@ func (gui *Gui) handleCheckoutRef(ref string) error {
}
}
+ if onDone != nil {
+ onDone()
+ }
+
gui.State.Panels.Branches.SelectedLine = 0
gui.State.Panels.Commits.SelectedLine = 0
return gui.refreshSidePanels(gui.g)
@@ -182,7 +189,7 @@ func (gui *Gui) handleCheckoutRef(ref string) error {
func (gui *Gui) handleCheckoutByName(g *gocui.Gui, v *gocui.View) error {
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("BranchName")+":", "", func(g *gocui.Gui, v *gocui.View) error {
- return gui.handleCheckoutRef(gui.trimmedContent(v))
+ return gui.handleCheckoutRef(gui.trimmedContent(v), nil)
})
}
diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go
index a5e864dc9..88ec76d04 100644
--- a/pkg/gui/commits_panel.go
+++ b/pkg/gui/commits_panel.go
@@ -540,7 +540,7 @@ func (gui *Gui) handleCheckoutCommit(g *gocui.Gui, v *gocui.View) error {
}
return gui.createConfirmationPanel(g, gui.getCommitsView(), true, gui.Tr.SLocalize("checkoutCommit"), gui.Tr.SLocalize("SureCheckoutThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
- return gui.handleCheckoutRef(commit.Sha)
+ return gui.handleCheckoutRef(commit.Sha, nil)
}, nil)
}
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
index 1ae9199eb..fd4e6bfd4 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -214,6 +214,21 @@ type guiState struct {
PrevMainWidth int
PrevMainHeight int
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
}
// for now the split view will always be on
diff --git a/pkg/gui/reflog_panel.go b/pkg/gui/reflog_panel.go
index 27b9a5590..fb1aa3978 100644
--- a/pkg/gui/reflog_panel.go
+++ b/pkg/gui/reflog_panel.go
@@ -85,7 +85,7 @@ func (gui *Gui) handleCheckoutReflogCommit(g *gocui.Gui, v *gocui.View) error {
}
err := gui.createConfirmationPanel(g, gui.getCommitsView(), true, gui.Tr.SLocalize("checkoutCommit"), gui.Tr.SLocalize("SureCheckoutThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
- return gui.handleCheckoutRef(commit.Sha)
+ return gui.handleCheckoutRef(commit.Sha, nil)
}, nil)
if err != nil {
return err
@@ -104,29 +104,58 @@ func (gui *Gui) handleCreateReflogResetMenu(g *gocui.Gui, v *gocui.View) error {
type reflogAction struct {
regexStr string
- action func(match []string, commitSha string, prevCommitSha string) (bool, error)
+ action func(match []string, commitSha string, prevCommitSha string, onDone func()) (bool, error)
+}
+
+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 {
+ reflogCommits := gui.State.ReflogCommits
+
reflogActions := []reflogAction{
{
regexStr: `^checkout: moving from ([\S]+)`,
- action: func(match []string, commitSha string, prevCommitSha string) (bool, error) {
+ action: func(match []string, commitSha string, prevCommitSha string, onDone func()) (bool, error) {
if len(match) <= 1 {
return false, nil
}
- return true, gui.handleCheckoutRef(match[1])
+ return true, gui.handleCheckoutRef(match[1], onDone)
},
},
{
regexStr: `^commit|^rebase -i \(start\)`,
- action: func(match []string, commitSha string, prevCommitSha string) (bool, error) {
- return true, gui.handleHardResetWithAutoStash(prevCommitSha)
+ action: func(match []string, commitSha string, prevCommitSha string, onDone func()) (bool, error) {
+ return true, gui.handleHardResetWithAutoStash(prevCommitSha, onDone)
},
},
}
- for i, reflogCommit := range 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 {
+ startIndex = 0
+ }
+
+ for offsetIdx, reflogCommit := range reflogCommits[startIndex:] {
+ i := offsetIdx + startIndex
for _, action := range reflogActions {
re := regexp.MustCompile(action.regexStr)
match := re.FindStringSubmatch(reflogCommit.Name)
@@ -134,24 +163,29 @@ func (gui *Gui) reflogUndo(g *gocui.Gui, v *gocui.View) error {
continue
}
prevCommitSha := ""
- if len(gui.State.ReflogCommits)-1 >= i+1 {
- prevCommitSha = gui.State.ReflogCommits[i+1].Sha
+ if len(reflogCommits)-1 >= i+1 {
+ prevCommitSha = reflogCommits[i+1].Sha
}
- done, err := action.action(match, reflogCommit.Sha, prevCommitSha)
- if err != nil {
- return err
+ nextKey := gui.reflogKey(gui.State.ReflogCommits[i+1])
+ onDone := func() {
+ gui.setUndoReflogKey(nextKey)
}
- if done {
- return nil
+
+ isMatchingAction, err := action.action(match, reflogCommit.Sha, prevCommitSha, onDone)
+ if !isMatchingAction {
+ continue
}
+
+ return err
}
}
return nil
}
-func (gui *Gui) handleHardResetWithAutoStash(commitSha string) error {
+// only to be used in the undo flow for now
+func (gui *Gui) handleHardResetWithAutoStash(commitSha string, onDone func()) 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 {
@@ -170,6 +204,7 @@ func (gui *Gui) handleHardResetWithAutoStash(commitSha string) error {
if err := gui.resetToRef(commitSha, "hard"); err != nil {
return gui.createErrorPanel(g, err.Error())
}
+ onDone()
if err := gui.GitCommand.StashDo(0, "pop"); err != nil {
if err := gui.refreshSidePanels(g); err != nil {
@@ -184,6 +219,6 @@ func (gui *Gui) handleHardResetWithAutoStash(commitSha string) error {
if err := gui.resetToRef(commitSha, "hard"); err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
-
+ onDone()
return gui.refreshSidePanels(gui.g)
}
diff --git a/pkg/gui/remote_branches_panel.go b/pkg/gui/remote_branches_panel.go
index 48aa62b5b..28270dac2 100644
--- a/pkg/gui/remote_branches_panel.go
+++ b/pkg/gui/remote_branches_panel.go
@@ -76,7 +76,7 @@ func (gui *Gui) handleCheckoutRemoteBranch(g *gocui.Gui, v *gocui.View) error {
if remoteBranch == nil {
return nil
}
- if err := gui.handleCheckoutRef(remoteBranch.RemoteName + "/" + remoteBranch.Name); err != nil {
+ if err := gui.handleCheckoutRef(remoteBranch.RemoteName+"/"+remoteBranch.Name, nil); err != nil {
return err
}
return gui.switchBranchesPanelContext("local-branches")
diff --git a/pkg/gui/tags_panel.go b/pkg/gui/tags_panel.go
index 719f0f848..b92727e8c 100644
--- a/pkg/gui/tags_panel.go
+++ b/pkg/gui/tags_panel.go
@@ -81,7 +81,7 @@ func (gui *Gui) handleCheckoutTag(g *gocui.Gui, v *gocui.View) error {
if tag == nil {
return nil
}
- if err := gui.handleCheckoutRef(tag.Name); err != nil {
+ if err := gui.handleCheckoutRef(tag.Name, nil); err != nil {
return err
}
return gui.switchBranchesPanelContext("local-branches")
diff --git a/pkg/gui/tasks_adapter.go b/pkg/gui/tasks_adapter.go
index f9ca49fff..f966f2eee 100644
--- a/pkg/gui/tasks_adapter.go
+++ b/pkg/gui/tasks_adapter.go
@@ -81,7 +81,6 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager {
},
func() {
gui.g.Update(func(*gocui.Gui) error {
- gui.Log.Warn("updating view")
return nil
})
})