1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-06-17 00:18:05 +02:00

add various interactive rebase commands

This commit is contained in:
Jesse Duffield
2019-02-18 23:27:54 +11:00
parent 76a27f417f
commit d44638130c
7 changed files with 178 additions and 39 deletions

View File

@ -121,7 +121,7 @@ func (app *App) Run() error {
}
func (app *App) Rebase() error {
app.Log.Error("TEST")
app.Log.Error("Lazygit invokved as interactive rebase demon")
ioutil.WriteFile(".git/rebase-merge/git-rebase-todo", []byte(os.Getenv("LAZYGIT_REBASE_TODO")), 0644)

View File

@ -276,7 +276,7 @@ func (c *GitCommand) RebaseBranch(onto string) error {
return err
}
return c.OSCommand.RunCommand(fmt.Sprintf("git rebase %s %s ", onto, curBranch))
return c.OSCommand.RunCommand(fmt.Sprintf("git rebase --autoStash %s %s ", onto, curBranch))
}
// Fetch fetch git repo
@ -462,12 +462,22 @@ func (c *GitCommand) IsInMergeState() (bool, error) {
return strings.Contains(output, "conclude merge") || strings.Contains(output, "unmerged paths"), nil
}
func (c *GitCommand) IsInRebaseState() (bool, error) {
// RebaseMode returns "" for non-rebase mode, "normal" for normal rebase
// and "interactive" for interactive rebase
func (c *GitCommand) RebaseMode() (string, error) {
exists, err := c.OSCommand.FileExists(".git/rebase-apply")
if err != nil {
return false, err
return "", err
}
if exists {
return "normal", nil
}
exists, err = c.OSCommand.FileExists(".git/rebase-merge")
if exists {
return "interactive", err
} else {
return "", err
}
return exists, nil
}
// RemoveFile directly
@ -537,14 +547,21 @@ func (c *GitCommand) getMergeBase() (string, error) {
// GetRebasingCommits obtains the commits that we're in the process of rebasing
func (c *GitCommand) GetRebasingCommits() ([]*Commit, error) {
rebasing, err := c.IsInRebaseState()
rebaseMode, err := c.RebaseMode()
if err != nil {
return nil, err
}
if !rebasing {
switch rebaseMode {
case "normal":
return c.GetNormalRebasingCommits()
case "interactive":
return c.GetInteractiveRebasingCommits()
default:
return nil, nil
}
}
func (c *GitCommand) GetNormalRebasingCommits() ([]*Commit, error) {
rewrittenCount := 0
bytesContent, err := ioutil.ReadFile(".git/rebase-apply/rewritten")
if err == nil {
@ -585,6 +602,10 @@ func (c *GitCommand) GetRebasingCommits() ([]*Commit, error) {
return commits, nil
}
func (c *GitCommand) GetInteractiveRebasingCommits() ([]*Commit, error) {
return nil, nil
}
// assuming the file starts like this:
// From e93d4193e6dd45ca9cf3a5a273d7ba6cd8b8fb20 Mon Sep 17 00:00:00 2001
// From: Lazygit Tester <test@example.com>
@ -780,27 +801,55 @@ func (c *GitCommand) GenericMerge(commandType string, command string) error {
return c.OSCommand.RunCommand(gitCommand)
}
func (c *GitCommand) InteractiveRebase(commits []*Commit, index int, action string) (*exec.Cmd, error) {
ex, err := os.Executable() // get the executable path for git to use
func (c *GitCommand) RewordCommit(commits []*Commit, index int) (*exec.Cmd, error) {
todo, err := c.GenerateGenericRebaseTodo(commits, index, "reword")
if err != nil {
ex = os.Args[0] // fallback to the first call argument if needed
return nil, err
}
// assume for now they won't pick the bottom commit
c.Log.Warn(len(commits))
c.Log.Warn(index)
if len(commits) <= index+1 {
return c.PrepareInteractiveRebaseCommand(commits[index+1].Sha, todo)
}
func (c *GitCommand) MoveCommitDown(commits []*Commit, index int) error {
// we must ensure that we have at least two commits after the selected one
if len(commits) <= index+2 {
// assuming they aren't picking the bottom commit
// TODO: support more than say 30 commits and ensure this logic is correct, and i18n
return nil, errors.New("You cannot interactive rebase onto the first commit")
return errors.New("Not enough room")
}
todo := ""
for i, commit := range commits[0 : index+1] {
a := "pick"
if i == index {
a = action
orderedCommits := append(commits[0:index], commits[index+1], commits[index])
for _, commit := range orderedCommits {
todo = "pick " + commit.Sha + "\n" + todo
}
todo += a + " " + commit.Sha + "\n"
cmd, err := c.PrepareInteractiveRebaseCommand(commits[index+2].Sha, todo)
if err != nil {
return err
}
return c.OSCommand.RunPreparedCommand(cmd)
}
func (c *GitCommand) InteractiveRebase(commits []*Commit, index int, action string) error {
todo, err := c.GenerateGenericRebaseTodo(commits, index, action)
if err != nil {
return err
}
cmd, err := c.PrepareInteractiveRebaseCommand(commits[index+1].Sha, todo)
if err != nil {
return err
}
return c.OSCommand.RunPreparedCommand(cmd)
}
func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string) (*exec.Cmd, error) {
ex, err := os.Executable() // get the executable path for git to use
if err != nil {
ex = os.Args[0] // fallback to the first call argument if needed
}
debug := "FALSE"
@ -808,7 +857,7 @@ func (c *GitCommand) InteractiveRebase(commits []*Commit, index int, action stri
debug = "TRUE"
}
splitCmd := str.ToArgv(fmt.Sprintf("git rebase --interactive %s", commits[index+1].Sha))
splitCmd := str.ToArgv(fmt.Sprintf("git rebase --autoStash --interactive %s", baseSha))
cmd := exec.Command(splitCmd[0], splitCmd[1:]...)
@ -823,18 +872,27 @@ func (c *GitCommand) InteractiveRebase(commits []*Commit, index int, action stri
"GIT_SEQUENCE_EDITOR="+ex,
)
if true {
return cmd, nil
}
out, err := cmd.CombinedOutput()
outString := string(out)
c.Log.Info(outString)
if err != nil {
if len(outString) == 0 {
return nil, err
func (c *GitCommand) HardReset(baseSha string) error {
return c.OSCommand.RunCommand("git reset --hard " + baseSha)
}
return nil, errors.New(outString)
func (v *GitCommand) GenerateGenericRebaseTodo(commits []*Commit, index int, action string) (string, error) {
if len(commits) <= index+1 {
// assuming they aren't picking the bottom commit
// TODO: support more than say 30 commits and ensure this logic is correct, and i18n
return "", errors.New("You cannot interactive rebase onto the first commit")
}
return nil, nil
todo := ""
for i, commit := range commits[0 : index+1] {
a := "pick"
if i == index {
a = action
}
todo = a + " " + commit.Sha + "\n" + todo
}
return todo, nil
}

View File

@ -247,3 +247,19 @@ func (c *OSCommand) FileExists(path string) (bool, error) {
}
return true, nil
}
// RunPreparedCommand takes a pointer to an exec.Cmd and runs it
// this is useful if you need to give your command some environment variables
// before running it
func (c *OSCommand) RunPreparedCommand(cmd *exec.Cmd) error {
out, err := cmd.CombinedOutput()
outString := string(out)
c.Log.Info(outString)
if err != nil {
if len(outString) == 0 {
return err
}
return errors.New(outString)
}
return nil
}

View File

@ -181,17 +181,45 @@ func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleRenameCommitEditor(g *gocui.Gui, v *gocui.View) error {
subProcess, err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "reword")
subProcess, err := gui.GitCommand.RewordCommit(gui.State.Commits, gui.State.Panels.Commits.SelectedLine)
if err != nil {
return err
return gui.createErrorPanel(gui.g, err.Error())
}
if subProcess != nil {
gui.SubProcess = subProcess
// g.Update(func(g *gocui.Gui) error {
// return gui.Errors.ErrSubProcess
// })
return gui.Errors.ErrSubProcess
}
return nil
}
func (gui *Gui) handleCommitDelete(g *gocui.Gui, v *gocui.View) error {
// TODO: i18n
return gui.createConfirmationPanel(gui.g, v, "Delete Commit", "Are you sure you want to delete this commit?", func(*gocui.Gui, *gocui.View) error {
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "drop")
return gui.handleGenericMergeCommandResult(err)
}, nil)
}
func (gui *Gui) handleCommitMoveDown(g *gocui.Gui, v *gocui.View) error {
gui.State.Panels.Commits.SelectedLine++
err := gui.GitCommand.MoveCommitDown(gui.State.Commits, gui.State.Panels.Commits.SelectedLine-1)
return gui.handleGenericMergeCommandResult(err)
}
func (gui *Gui) handleCommitMoveUp(g *gocui.Gui, v *gocui.View) error {
if gui.State.Panels.Commits.SelectedLine == 0 {
return gui.createErrorPanel(gui.g, "You cannot move the topmost commit up") // TODO: i18n
}
gui.State.Panels.Commits.SelectedLine--
err := gui.GitCommand.MoveCommitDown(gui.State.Commits, gui.State.Panels.Commits.SelectedLine)
return gui.handleGenericMergeCommandResult(err)
}
func (gui *Gui) handleCommitEdit(g *gocui.Gui, v *gocui.View) error {
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "edit")
return gui.handleGenericMergeCommandResult(err)
}

View File

@ -310,6 +310,30 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Modifier: gocui.ModNone,
Handler: gui.handleCommitFixup,
Description: gui.Tr.SLocalize("fixupCommit"),
}, {
ViewName: "commits",
Key: 'd',
Modifier: gocui.ModNone,
Handler: gui.handleCommitDelete,
Description: gui.Tr.SLocalize("deleteCommit"),
}, {
ViewName: "commits",
Key: 'J',
Modifier: gocui.ModNone,
Handler: gui.handleCommitMoveDown,
Description: gui.Tr.SLocalize("moveDownCommit"),
}, {
ViewName: "commits",
Key: 'K',
Modifier: gocui.ModNone,
Handler: gui.handleCommitMoveUp,
Description: gui.Tr.SLocalize("moveUpCommit"),
}, {
ViewName: "commits",
Key: 'e',
Modifier: gocui.ModNone,
Handler: gui.handleCommitEdit,
Description: gui.Tr.SLocalize("editCommit"),
}, {
ViewName: "stash",
Key: gocui.KeySpace,

View File

@ -94,11 +94,11 @@ func (gui *Gui) updateWorkTreeState() error {
gui.State.WorkingTreeState = "merging"
return nil
}
rebasing, err := gui.GitCommand.IsInRebaseState()
rebaseMode, err := gui.GitCommand.RebaseMode()
if err != nil {
return err
}
if rebasing {
if rebaseMode != "" {
gui.State.WorkingTreeState = "rebasing"
return nil
}

View File

@ -283,6 +283,19 @@ func addEnglish(i18nObject *i18n.Bundle) error {
ID: "renameCommit",
Other: "rename commit",
}, &i18n.Message{
ID: "deleteCommit",
Other: "delete commit", // TODO: other languages
}, &i18n.Message{
ID: "moveDownCommit",
Other: "move commit down one", // TODO: other languages
}, &i18n.Message{
ID: "moveUpCommit",
Other: "move commit up one", // TODO: other languages
}, &i18n.Message{
ID: "editCommit",
Other: "edit commit", // TODO: other languages
}, &i18n.Message{
ID: "renameCommitEditor",
Other: "rename commit with editor",
}, &i18n.Message{