From 95d451e59a419933f6e5f90305ffc6fe8dbc448f Mon Sep 17 00:00:00 2001 From: Jesse Duffield Duffield Date: Sun, 24 Feb 2019 09:42:24 +1100 Subject: [PATCH] Make it easier to run sync/async commands, switch to interactive rebase when rebasing on branches --- pkg/commands/git.go | 42 +++++++++++++++++++++++++-------- pkg/gui/commit_message_panel.go | 26 +++++++++++++------- pkg/gui/commits_panel.go | 10 ++++---- pkg/gui/files_panel.go | 22 +++++------------ pkg/i18n/english.go | 14 +++++------ 5 files changed, 68 insertions(+), 46 deletions(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index b042e24da..a982f032b 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -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))) } -func (c *GitCommand) RebaseBranch(onto string) error { - curBranch, err := c.CurrentBranchName() +// RebaseBranch interactive rebases onto a branch +func (c *GitCommand) RebaseBranch(branchName string) error { + cmd, err := c.PrepareInteractiveRebaseCommand(branchName, "", false) if err != nil { return err } - return c.OSCommand.RunCommand(fmt.Sprintf("git rebase --autostash %s %s ", onto, curBranch)) + return c.OSCommand.RunPreparedCommand(cmd) } // Fetch fetch git repo @@ -332,12 +333,18 @@ func (c *GitCommand) usingGpg() bool { } // Commit commits to git -func (c *GitCommand) Commit(message string, amend bool) (*exec.Cmd, error) { - amendParam := "" - if amend { - amendParam = " --amend" +func (c *GitCommand) Commit(message string) (*exec.Cmd, error) { + command := fmt.Sprintf("git commit -m %s", c.OSCommand.Quote(message)) + if c.usingGpg() { + 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() { 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:]...) + gitSequenceEditor := ex + if todo == "" { + gitSequenceEditor = "true" + } + cmd.Env = os.Environ() cmd.Env = append( cmd.Env, @@ -652,7 +664,7 @@ func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string "DEBUG="+debug, "LANG=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 { @@ -706,7 +718,17 @@ func (c *GitCommand) EditRebaseTodo(index int, action string) error { 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], " ") content[contentIndex] = action + " " + strings.Join(splitLine[1:], " ") result := strings.Join(content, "\n") diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go index 130ec3401..b05a7df6c 100644 --- a/pkg/gui/commit_message_panel.go +++ b/pkg/gui/commit_message_panel.go @@ -1,28 +1,38 @@ package gui import ( + "os/exec" "strconv" "strings" "github.com/jesseduffield/gocui" ) -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")) - } - sub, err := gui.GitCommand.Commit(message, false) +// runSyncOrAsyncCommand takes the output of a command that may have returned +// either no error, an error, or a subprocess to execute, and if a subprocess +// needs to be set on the gui object, it does so, and then returns the error +func (gui *Gui) runSyncOrAsyncCommand(sub *exec.Cmd, err error) error { if err != nil { - // TODO need to find a way to send through this error if err != gui.Errors.ErrSubProcess { - return gui.createErrorPanel(g, err.Error()) + return gui.createErrorPanel(gui.g, err.Error()) } } if sub != nil { gui.SubProcess = sub 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.SetCursor(0, 0) _ = v.SetOrigin(0, 0) diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index a2f3e858c..383f9b6ba 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -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 // begin a rebase. It then updates the todo file with that action 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 // 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 @@ -220,10 +225,6 @@ func (gui *Gui) handleMidRebaseCommand(action string) (bool, error) { 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 { 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 { + // 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 { err := gui.GitCommand.AmendTo(gui.State.Commits[gui.State.Panels.Commits.SelectedLine].Sha) return gui.handleGenericMergeCommandResult(err) diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index c6f5cd7d5..1f57e91e8 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -287,18 +287,16 @@ func (gui *Gui) handleAmendCommitPress(g *gocui.Gui, filesView *gocui.View) erro if len(gui.stagedFiles()) == 0 && gui.State.WorkingTreeState == "normal" { 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 { 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 { - lastCommitMsg := gui.State.Commits[0].Name - _, err := gui.GitCommand.Commit(lastCommitMsg, true) - if err != nil { - return gui.createErrorPanel(g, err.Error()) + if err := gui.runSyncOrAsyncCommand(gui.GitCommand.AmendHead()); err != nil { + return err } return gui.refreshSidePanels(g) @@ -324,15 +322,7 @@ func (gui *Gui) PrepareSubProcess(g *gocui.Gui, commands ...string) { } func (gui *Gui) editFile(filename string) error { - sub, err := 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 + return gui.runSyncOrAsyncCommand(gui.OSCommand.EditFile(filename)) } func (gui *Gui) handleFileEdit(g *gocui.Gui, v *gocui.View) error { diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index ba137a3be..f302cd650 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -65,7 +65,7 @@ func addEnglish(i18nObject *i18n.Bundle) error { Other: "amend last commit", }, &i18n.Message{ 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{ ID: "NoCommitToAmend", Other: "There's no commit to amend.", @@ -284,7 +284,7 @@ func addEnglish(i18nObject *i18n.Bundle) error { Other: "Fixup", }, &i18n.Message{ 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{ ID: "SureSquashThisCommit", Other: "Are you sure you want to squash this commit into the commit below?", // TODO: i18n @@ -568,11 +568,9 @@ func addEnglish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "YouAreHere", Other: "YOU ARE HERE", - }, &i18n.Message{ - ID: "rewordNotSupported", - Other: "rewording commits while interactively rebasing is not currently supported", - }, - - + }, &i18n.Message{ + ID: "rewordNotSupported", + Other: "rewording commits while interactively rebasing is not currently supported", + }, ) }