Make it easier to run sync/async commands, switch to interactive rebase when rebasing on branches

This commit is contained in:
Jesse Duffield Duffield 2019-02-24 09:42:24 +11:00
parent 6c1d2d45ef
commit 95d451e59a
5 changed files with 68 additions and 46 deletions

View file

@ -252,13 +252,14 @@ func (c *GitCommand) RenameCommit(name string) error {
return c.OSCommand.RunCommand(fmt.Sprintf("git commit --allow-empty --amend -m %s", c.OSCommand.Quote(name))) return c.OSCommand.RunCommand(fmt.Sprintf("git commit --allow-empty --amend -m %s", c.OSCommand.Quote(name)))
} }
func (c *GitCommand) RebaseBranch(onto string) error { // RebaseBranch interactive rebases onto a branch
curBranch, err := c.CurrentBranchName() func (c *GitCommand) RebaseBranch(branchName string) error {
cmd, err := c.PrepareInteractiveRebaseCommand(branchName, "", false)
if err != nil { if err != nil {
return err return err
} }
return c.OSCommand.RunCommand(fmt.Sprintf("git rebase --autostash %s %s ", onto, curBranch)) return c.OSCommand.RunPreparedCommand(cmd)
} }
// Fetch fetch git repo // Fetch fetch git repo
@ -332,12 +333,18 @@ func (c *GitCommand) usingGpg() bool {
} }
// Commit commits to git // Commit commits to git
func (c *GitCommand) Commit(message string, amend bool) (*exec.Cmd, error) { func (c *GitCommand) Commit(message string) (*exec.Cmd, error) {
amendParam := "" command := fmt.Sprintf("git commit -m %s", c.OSCommand.Quote(message))
if amend { if c.usingGpg() {
amendParam = " --amend" return c.OSCommand.PrepareSubProcess(c.OSCommand.Platform.shell, c.OSCommand.Platform.shellArg, command), nil
} }
command := fmt.Sprintf("git commit%s -m %s", amendParam, c.OSCommand.Quote(message))
return nil, c.OSCommand.RunCommand(command)
}
// AmendHead amends HEAD with whatever is staged in your working tree
func (c *GitCommand) AmendHead() (*exec.Cmd, error) {
command := "git commit --amend --no-edit"
if c.usingGpg() { if c.usingGpg() {
return c.OSCommand.PrepareSubProcess(c.OSCommand.Platform.shell, c.OSCommand.Platform.shellArg, command), nil return c.OSCommand.PrepareSubProcess(c.OSCommand.Platform.shell, c.OSCommand.Platform.shellArg, command), nil
} }
@ -644,6 +651,11 @@ func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string
cmd := exec.Command(splitCmd[0], splitCmd[1:]...) cmd := exec.Command(splitCmd[0], splitCmd[1:]...)
gitSequenceEditor := ex
if todo == "" {
gitSequenceEditor = "true"
}
cmd.Env = os.Environ() cmd.Env = os.Environ()
cmd.Env = append( cmd.Env = append(
cmd.Env, cmd.Env,
@ -652,7 +664,7 @@ func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string
"DEBUG="+debug, "DEBUG="+debug,
"LANG=en_US.UTF-8", // Force using EN as language "LANG=en_US.UTF-8", // Force using EN as language
"LC_ALL=en_US.UTF-8", // Force using EN as language "LC_ALL=en_US.UTF-8", // Force using EN as language
"GIT_SEQUENCE_EDITOR="+ex, "GIT_SEQUENCE_EDITOR="+gitSequenceEditor,
) )
if overrideEditor { if overrideEditor {
@ -706,7 +718,17 @@ func (c *GitCommand) EditRebaseTodo(index int, action string) error {
content := strings.Split(string(bytes), "\n") content := strings.Split(string(bytes), "\n")
contentIndex := len(content) - 2 - index // count lines that are not blank and are not comments
commitCount := 0
for _, line := range content {
if line != "" && !strings.HasPrefix(line, "#") {
commitCount++
}
}
// we have the most recent commit at the bottom whereas the todo file has
// it at the bottom, so we need to subtract our index from the commit count
contentIndex := commitCount - 1 - index
splitLine := strings.Split(content[contentIndex], " ") splitLine := strings.Split(content[contentIndex], " ")
content[contentIndex] = action + " " + strings.Join(splitLine[1:], " ") content[contentIndex] = action + " " + strings.Join(splitLine[1:], " ")
result := strings.Join(content, "\n") result := strings.Join(content, "\n")

View file

@ -1,28 +1,38 @@
package gui package gui
import ( import (
"os/exec"
"strconv" "strconv"
"strings" "strings"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
) )
func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error { // runSyncOrAsyncCommand takes the output of a command that may have returned
message := gui.trimmedContent(v) // either no error, an error, or a subprocess to execute, and if a subprocess
if message == "" { // needs to be set on the gui object, it does so, and then returns the error
return gui.createErrorPanel(g, gui.Tr.SLocalize("CommitWithoutMessageErr")) func (gui *Gui) runSyncOrAsyncCommand(sub *exec.Cmd, err error) error {
}
sub, err := gui.GitCommand.Commit(message, false)
if err != nil { if err != nil {
// TODO need to find a way to send through this error
if err != gui.Errors.ErrSubProcess { if err != gui.Errors.ErrSubProcess {
return gui.createErrorPanel(g, err.Error()) return gui.createErrorPanel(gui.g, err.Error())
} }
} }
if sub != nil { if sub != nil {
gui.SubProcess = sub gui.SubProcess = sub
return gui.Errors.ErrSubProcess return gui.Errors.ErrSubProcess
} }
return nil
}
func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error {
message := gui.trimmedContent(v)
if message == "" {
return gui.createErrorPanel(g, gui.Tr.SLocalize("CommitWithoutMessageErr"))
}
if err := gui.runSyncOrAsyncCommand(gui.GitCommand.Commit(message)); err != nil {
return err
}
v.Clear() v.Clear()
_ = v.SetCursor(0, 0) _ = v.SetCursor(0, 0)
_ = v.SetOrigin(0, 0) _ = v.SetOrigin(0, 0)

View file

@ -212,6 +212,11 @@ func (gui *Gui) handleRenameCommitEditor(g *gocui.Gui, v *gocui.View) error {
// commit meaning you are trying to edit the todo file rather than actually // commit meaning you are trying to edit the todo file rather than actually
// begin a rebase. It then updates the todo file with that action // begin a rebase. It then updates the todo file with that action
func (gui *Gui) handleMidRebaseCommand(action string) (bool, error) { func (gui *Gui) handleMidRebaseCommand(action string) (bool, error) {
selectedCommit := gui.State.Commits[gui.State.Panels.Commits.SelectedLine]
if selectedCommit.Status != "rebasing" {
return false, nil
}
// for now we do not support setting 'reword' because it requires an editor // for now we do not support setting 'reword' because it requires an editor
// and that means we either unconditionally wait around for the subprocess to ask for // and that means we either unconditionally wait around for the subprocess to ask for
// our input or we set a lazygit client as the EDITOR env variable and have it // our input or we set a lazygit client as the EDITOR env variable and have it
@ -220,10 +225,6 @@ func (gui *Gui) handleMidRebaseCommand(action string) (bool, error) {
return true, gui.createErrorPanel(gui.g, gui.Tr.SLocalize("rewordNotSupported")) return true, gui.createErrorPanel(gui.g, gui.Tr.SLocalize("rewordNotSupported"))
} }
selectedCommit := gui.State.Commits[gui.State.Panels.Commits.SelectedLine]
if selectedCommit.Status != "rebasing" {
return false, nil
}
if err := gui.GitCommand.EditRebaseTodo(gui.State.Panels.Commits.SelectedLine, action); err != nil { if err := gui.GitCommand.EditRebaseTodo(gui.State.Panels.Commits.SelectedLine, action); err != nil {
return false, gui.createErrorPanel(gui.g, err.Error()) return false, gui.createErrorPanel(gui.g, err.Error())
} }
@ -318,6 +319,7 @@ func (gui *Gui) handleCommitEdit(g *gocui.Gui, v *gocui.View) error {
} }
func (gui *Gui) handleCommitAmendTo(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleCommitAmendTo(g *gocui.Gui, v *gocui.View) error {
// TODO: i18n
return gui.createConfirmationPanel(gui.g, v, "Amend Commit", "Are you sure you want to amend this commit with your staged files?", func(*gocui.Gui, *gocui.View) error { return gui.createConfirmationPanel(gui.g, v, "Amend Commit", "Are you sure you want to amend this commit with your staged files?", func(*gocui.Gui, *gocui.View) error {
err := gui.GitCommand.AmendTo(gui.State.Commits[gui.State.Panels.Commits.SelectedLine].Sha) err := gui.GitCommand.AmendTo(gui.State.Commits[gui.State.Panels.Commits.SelectedLine].Sha)
return gui.handleGenericMergeCommandResult(err) return gui.handleGenericMergeCommandResult(err)

View file

@ -287,18 +287,16 @@ func (gui *Gui) handleAmendCommitPress(g *gocui.Gui, filesView *gocui.View) erro
if len(gui.stagedFiles()) == 0 && gui.State.WorkingTreeState == "normal" { if len(gui.stagedFiles()) == 0 && gui.State.WorkingTreeState == "normal" {
return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit")) return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit"))
} }
title := strings.Title(gui.Tr.SLocalize("AmendLastCommit"))
question := gui.Tr.SLocalize("SureToAmend")
if len(gui.State.Commits) == 0 { if len(gui.State.Commits) == 0 {
return gui.createErrorPanel(g, gui.Tr.SLocalize("NoCommitToAmend")) return gui.createErrorPanel(g, gui.Tr.SLocalize("NoCommitToAmend"))
} }
title := strings.Title(gui.Tr.SLocalize("AmendLastCommit"))
question := gui.Tr.SLocalize("SureToAmend")
return gui.createConfirmationPanel(g, filesView, title, question, func(g *gocui.Gui, v *gocui.View) error { return gui.createConfirmationPanel(g, filesView, title, question, func(g *gocui.Gui, v *gocui.View) error {
lastCommitMsg := gui.State.Commits[0].Name if err := gui.runSyncOrAsyncCommand(gui.GitCommand.AmendHead()); err != nil {
_, err := gui.GitCommand.Commit(lastCommitMsg, true) return err
if err != nil {
return gui.createErrorPanel(g, err.Error())
} }
return gui.refreshSidePanels(g) return gui.refreshSidePanels(g)
@ -324,15 +322,7 @@ func (gui *Gui) PrepareSubProcess(g *gocui.Gui, commands ...string) {
} }
func (gui *Gui) editFile(filename string) error { func (gui *Gui) editFile(filename string) error {
sub, err := gui.OSCommand.EditFile(filename) return gui.runSyncOrAsyncCommand(gui.OSCommand.EditFile(filename))
if err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
if sub != nil {
gui.SubProcess = sub
return gui.Errors.ErrSubProcess
}
return nil
} }
func (gui *Gui) handleFileEdit(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleFileEdit(g *gocui.Gui, v *gocui.View) error {

View file

@ -65,7 +65,7 @@ func addEnglish(i18nObject *i18n.Bundle) error {
Other: "amend last commit", Other: "amend last commit",
}, &i18n.Message{ }, &i18n.Message{
ID: "SureToAmend", ID: "SureToAmend",
Other: "Are you sure you want to amend last commit? You can change commit message from commits panel.", Other: "Are you sure you want to amend last commit? Afterwards, you can change commit message from the commits panel.",
}, &i18n.Message{ }, &i18n.Message{
ID: "NoCommitToAmend", ID: "NoCommitToAmend",
Other: "There's no commit to amend.", Other: "There's no commit to amend.",
@ -284,7 +284,7 @@ func addEnglish(i18nObject *i18n.Bundle) error {
Other: "Fixup", Other: "Fixup",
}, &i18n.Message{ }, &i18n.Message{
ID: "SureFixupThisCommit", ID: "SureFixupThisCommit",
Other: "Are you sure you want to fixup this commit? The commit beneath will be squashed up into this one", Other: "Are you sure you want to 'fixup' this commit? It will be merged into the commit below",
}, &i18n.Message{ }, &i18n.Message{
ID: "SureSquashThisCommit", ID: "SureSquashThisCommit",
Other: "Are you sure you want to squash this commit into the commit below?", // TODO: i18n Other: "Are you sure you want to squash this commit into the commit below?", // TODO: i18n
@ -572,7 +572,5 @@ func addEnglish(i18nObject *i18n.Bundle) error {
ID: "rewordNotSupported", ID: "rewordNotSupported",
Other: "rewording commits while interactively rebasing is not currently supported", Other: "rewording commits while interactively rebasing is not currently supported",
}, },
) )
} }