1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-07-30 03:23:08 +03:00

more work on rebasing feature

This commit is contained in:
Jesse Duffield
2019-02-16 21:01:17 +11:00
parent ad93b4c863
commit e011e9bc42
15 changed files with 270 additions and 200 deletions

View File

@ -98,43 +98,6 @@ func (gui *Gui) handleBranchesPrevLine(g *gocui.Gui, v *gocui.View) error {
// specific functions
func (gui *Gui) handleRebase(g *gocui.Gui, v *gocui.View) error {
selectedBranch := gui.getSelectedBranch().Name
checkedOutBranch := gui.State.Branches[0].Name
title := "Rebasing"
prompt := fmt.Sprintf("Are you sure you want to rebase %s onto %s?", checkedOutBranch, selectedBranch)
return gui.createConfirmationPanel(g, v, title, prompt,
func(g *gocui.Gui, v *gocui.View) error {
if selectedBranch == checkedOutBranch {
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantRebaseOntoSelf"))
}
if err := gui.GitCommand.RebaseBranch(selectedBranch); err != nil {
if !strings.Contains(err.Error(), "When you have resolved this problem") {
return gui.createErrorPanel(gui.g, err.Error())
}
if err := gui.refreshSidePanels(g); err != nil {
return err
}
return gui.createConfirmationPanel(g, v, "Auto-rebase failed", gui.Tr.SLocalize("FoundConflicts"),
func(g *gocui.Gui, v *gocui.View) error {
return gui.refreshSidePanels(g)
}, func(g *gocui.Gui, v *gocui.View) error {
if err := gui.GitCommand.AbortRebaseBranch(); err != nil {
return err
}
return gui.refreshSidePanels(g)
},
)
}
return gui.refreshSidePanels(g)
}, nil)
}
func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error {
if gui.State.Panels.Branches.SelectedLine == -1 {
return nil
@ -263,28 +226,45 @@ func (gui *Gui) deleteNamedBranch(g *gocui.Gui, v *gocui.View, selectedBranch *c
}
func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error {
checkedOutBranch := gui.State.Branches[0]
selectedBranch := gui.getSelectedBranch()
defer gui.refreshSidePanels(g)
if checkedOutBranch.Name == selectedBranch.Name {
checkedOutBranch := gui.State.Branches[0].Name
selectedBranch := gui.getSelectedBranch().Name
if checkedOutBranch == selectedBranch {
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantMergeBranchIntoItself"))
}
if err := gui.GitCommand.Merge(selectedBranch.Name); err != nil {
if strings.Contains(err.Error(), "fix conflicts") {
return gui.createConfirmationPanel(g, v, "Auto-merge failed", gui.Tr.SLocalize("FoundConflicts"),
func(g *gocui.Gui, v *gocui.View) error {
return nil
}, func(g *gocui.Gui, v *gocui.View) error {
if err := gui.GitCommand.AbortMergeBranch(); err != nil {
return err
}
return gui.refreshSidePanels(g)
},
)
}
return gui.createErrorPanel(g, err.Error())
prompt := gui.Tr.TemplateLocalize(
"ConfirmMerge",
Teml{
"checkedOutBranch": checkedOutBranch,
"selectedBranch": selectedBranch,
},
)
return gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("MergingTitle"), prompt,
func(g *gocui.Gui, v *gocui.View) error {
err := gui.GitCommand.Merge(selectedBranch)
return gui.handleGenericMergeCommandResult(err)
}, nil)
}
func (gui *Gui) handleRebase(g *gocui.Gui, v *gocui.View) error {
checkedOutBranch := gui.State.Branches[0].Name
selectedBranch := gui.getSelectedBranch().Name
if selectedBranch == checkedOutBranch {
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantRebaseOntoSelf"))
}
return nil
prompt := gui.Tr.TemplateLocalize(
"ConfirmRebase",
Teml{
"checkedOutBranch": checkedOutBranch,
"selectedBranch": selectedBranch,
},
)
return gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("RebasingTitle"), prompt,
func(g *gocui.Gui, v *gocui.View) error {
err := gui.GitCommand.RebaseBranch(selectedBranch)
return gui.handleGenericMergeCommandResult(err)
}, nil)
}
func (gui *Gui) handleFastForward(g *gocui.Gui, v *gocui.View) error {
@ -296,10 +276,10 @@ func (gui *Gui) handleFastForward(g *gocui.Gui, v *gocui.View) error {
return nil
}
if branch.Pushables == "?" {
return gui.createErrorPanel(gui.g, "Cannot fast-forward a branch with no upstream")
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("FwdNoUpstream"))
}
if branch.Pushables != "0" {
return gui.createErrorPanel(gui.g, "Cannot fast-forward a branch with commits to push")
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("FwdCommitsToPush"))
}
upstream := "origin" // hardcoding for now
message := gui.Tr.TemplateLocalize(

76
pkg/gui/context.go Normal file
View File

@ -0,0 +1,76 @@
package gui
func (gui *Gui) titleMap() map[string]string {
return map[string]string{
"commits": gui.Tr.SLocalize("DiffTitle"),
"branches": gui.Tr.SLocalize("LogTitle"),
"files": gui.Tr.SLocalize("DiffTitle"),
"status": "",
"stash": gui.Tr.SLocalize("DiffTitle"),
}
}
func (gui *Gui) contextTitleMap() map[string]map[string]string {
return map[string]map[string]string{
"main": {
"staging": gui.Tr.SLocalize("StagingMainTitle"),
"merging": gui.Tr.SLocalize("MergingMainTitle"),
},
}
}
func (gui *Gui) setMainTitle() error {
currentViewName := gui.g.CurrentView().Name()
var newTitle string
if context, ok := gui.State.Contexts[currentViewName]; ok {
newTitle = gui.contextTitleMap()[currentViewName][context]
} else if title, ok := gui.titleMap()[currentViewName]; ok {
newTitle = title
} else {
return nil
}
gui.getMainView().Title = newTitle
return nil
}
func (gui *Gui) changeContext(viewName, context string) error {
// todo: store somewhere permanently
if gui.State.Contexts[viewName] == context {
return nil
}
contextMap := gui.getContextMap()
gui.g.DeleteKeybindings(viewName)
bindings := contextMap[viewName][context]
for _, binding := range bindings {
if err := gui.g.SetKeybinding(viewName, binding.Key, binding.Modifier, binding.Handler); err != nil {
return err
}
}
gui.State.Contexts[viewName] = context
gui.setMainTitle()
return nil
}
func (gui *Gui) setInitialContexts() error {
contextMap := gui.getContextMap()
initialContexts := map[string]string{
"main": "merging",
}
for viewName, context := range initialContexts {
bindings := contextMap[viewName][context]
for _, binding := range bindings {
if err := gui.g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, binding.Handler); err != nil {
return err
}
}
}
gui.State.Contexts = initialContexts
return nil
}

View File

@ -217,6 +217,9 @@ func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
return func(g *gocui.Gui) error {
v := gui.g.CurrentView()
if v != focusedView {
if err := gui.onFocusChange(); err != nil {
return err
}
if err := gui.onFocusLost(focusedView); err != nil {
return err
}
@ -229,6 +232,14 @@ func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
}
}
func (gui *Gui) onFocusChange() error {
currentView := gui.g.CurrentView()
for _, view := range gui.g.Views() {
view.Highlight = view == currentView
}
return gui.setMainTitle()
}
func (gui *Gui) onFocusLost(v *gocui.View) error {
if v == nil {
return nil
@ -306,31 +317,6 @@ func (gui *Gui) layout(g *gocui.Gui) error {
v.FgColor = gocui.ColorWhite
}
// v, err = g.SetView("staging", leftSideWidth+panelSpacing, 0, width-1, optionsTop, gocui.LEFT)
// if err != nil {
// if err.Error() != "unknown view" {
// return err
// }
// v.Title = gui.Tr.SLocalize("StagingTitle")
// v.Highlight = true
// v.FgColor = gocui.ColorWhite
// if _, err := g.SetViewOnBottom("staging"); err != nil {
// return err
// }
// }
// v, err = g.SetView("merging", leftSideWidth+panelSpacing, 0, width-1, optionsTop, gocui.LEFT)
// if err != nil {
// if err.Error() != "unknown view" {
// return err
// }
// v.Title = gui.Tr.SLocalize("MergingTitle")
// v.FgColor = gocui.ColorWhite
// if _, err := g.SetViewOnBottom("merging"); err != nil {
// return err
// }
// }
if v, err := g.SetView("status", 0, 0, leftSideWidth, statusFilesBoundary, gocui.BOTTOM|gocui.RIGHT); err != nil {
if err.Error() != "unknown view" {
return err

View File

@ -417,48 +417,6 @@ func (gui *Gui) keybindings(g *gocui.Gui) error {
return nil
}
func (gui *Gui) setInitialContexts() error {
contextMap := gui.getContextMap()
initialContexts := map[string]string{
"main": "merging",
}
for viewName, context := range initialContexts {
bindings := contextMap[viewName][context]
for _, binding := range bindings {
if err := gui.g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, binding.Handler); err != nil {
return err
}
}
}
gui.State.Contexts = initialContexts
return nil
}
func (gui *Gui) changeContext(viewName, context string) error {
// todo: store somewhere permanently
if gui.State.Contexts[viewName] == context {
return nil
}
contextMap := gui.getContextMap()
gui.g.DeleteKeybindings(viewName)
bindings := contextMap[viewName][context]
for _, binding := range bindings {
if err := gui.g.SetKeybinding(viewName, binding.Key, binding.Modifier, binding.Handler); err != nil {
return err
}
}
gui.State.Contexts[viewName] = context
return nil
}
func (gui *Gui) getContextMap() map[string]map[string][]*Binding {
return map[string]map[string][]*Binding{
"main": {

View File

@ -270,27 +270,16 @@ func (gui *Gui) handleCompleteMerge() error {
filesView := gui.getFilesView()
gui.stageSelectedFile(gui.g)
gui.refreshFiles()
// if we got conflicts after unstashing, we don't want to call any git
// commands to continue rebasing/merging here
if gui.State.WorkingTreeState == "normal" {
return gui.handleEscapeMerge(gui.g, gui.getMainView())
}
// if there are no more files with merge conflicts, we should ask whether the user wants to continue
if !gui.anyFilesWithMergeConflicts() {
// ask if user wants to continue
if err := gui.createConfirmationPanel(gui.g, filesView, "continue", gui.Tr.SLocalize("ConflictsResolved"), func(g *gocui.Gui, v *gocui.View) error {
if err := gui.genericRebaseCommand("continue"); err != nil {
if err == gui.Errors.ErrSubProcess {
return err
}
if strings.Contains(err.Error(), "No changes - did you forget to use") {
if err := gui.genericRebaseCommand("skip"); err != nil {
if err == gui.Errors.ErrSubProcess {
return err
}
gui.createErrorPanel(gui.g, err.Error())
}
} else {
// HERE is the place for this special error panel
gui.createErrorPanel(gui.g, err.Error())
}
}
return gui.refreshSidePanels(gui.g)
return gui.genericMergeCommand("continue")
}, nil); err != nil {
return err
}

View File

@ -28,11 +28,7 @@ func (gui *Gui) handleCreateRebaseOptionsMenu(g *gocui.Gui, v *gocui.View) error
handleMenuPress := func(index int) error {
command := options[index].value
err := gui.genericRebaseCommand(command)
if err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
return nil
return gui.genericMergeCommand(command)
}
var title string
@ -45,7 +41,7 @@ func (gui *Gui) handleCreateRebaseOptionsMenu(g *gocui.Gui, v *gocui.View) error
return gui.createMenu(title, options, handleMenuPress)
}
func (gui *Gui) genericRebaseCommand(command string) error {
func (gui *Gui) genericMergeCommand(command string) error {
status := gui.State.WorkingTreeState
if status != "merging" && status != "rebasing" {
@ -56,7 +52,8 @@ func (gui *Gui) genericRebaseCommand(command string) error {
// we should end up with a command like 'git merge --continue'
// it's impossible for a rebase to require a commit so we'll use a subprocess only if it's a merge
if status == "merging" {
// TODO: find a way to make the commit automatic
if status == "merging" && command != "abort" && gui.Config.GetUserConfig().GetBool("git.merging.manualCommit") {
sub := gui.OSCommand.PrepareSubProcess("git", commandType, fmt.Sprintf("--%s", command))
if sub != nil {
gui.SubProcess = sub
@ -64,6 +61,33 @@ func (gui *Gui) genericRebaseCommand(command string) error {
}
return nil
}
return gui.OSCommand.RunCommand(fmt.Sprintf("git %s --%s", commandType, command))
result := gui.GitCommand.GenericMerge(commandType, command)
if err := gui.handleGenericMergeCommandResult(result); err != nil {
return err
}
return nil
}
func (gui *Gui) handleGenericMergeCommandResult(result error) error {
if err := gui.refreshSidePanels(gui.g); err != nil {
return err
}
if result == nil {
return nil
} else if result == gui.Errors.ErrSubProcess {
return result
} else if strings.Contains(result.Error(), "No changes - did you forget to use") {
return gui.genericMergeCommand("skip")
} else if strings.Contains(result.Error(), "When you have resolved this problem") || strings.Contains(result.Error(), "fix conflicts") {
// TODO: generalise this title to support merging and rebasing
return gui.createConfirmationPanel(gui.g, gui.getFilesView(), gui.Tr.SLocalize("FoundConflictsTitle"), gui.Tr.SLocalize("FoundConflicts"),
func(g *gocui.Gui, v *gocui.View) error {
return nil
}, func(g *gocui.Gui, v *gocui.View) error {
return gui.genericMergeCommand("abort")
},
)
} else {
return gui.createErrorPanel(gui.g, result.Error())
}
}

View File

@ -127,19 +127,11 @@ func (gui *Gui) returnFocus(g *gocui.Gui, v *gocui.View) error {
}
// pass in oldView = nil if you don't want to be able to return to your old view
// TODO: move some of this logic into our onFocusLost and onFocus hooks
func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error {
// we assume we'll never want to return focus to a confirmation panel i.e.
// we should never stack confirmation panels
if oldView != nil && oldView.Name() != "confirmation" {
oldView.Highlight = false
message := gui.Tr.TemplateLocalize(
"settingPreviewsViewTo",
Teml{
"oldViewName": oldView.Name(),
},
)
gui.Log.Info(message)
// second class panels should never have focus restored to them because
// once they lose focus they are effectively 'destroyed'
secondClassPanels := []string{"confirmation", "menu"}
@ -148,7 +140,7 @@ func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error {
}
}
newView.Highlight = true
gui.Log.Info("setting highlight to true for view" + newView.Name())
message := gui.Tr.TemplateLocalize(
"newFocusedViewIs",
Teml{