summaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2019-02-18 23:27:54 +1100
committerJesse Duffield <jessedduffield@gmail.com>2019-02-18 23:27:54 +1100
commitd44638130c8d07c648b45796cc6b0dff221c7d82 (patch)
tree71b1bfdc271de1cac8fffe223d23fe7295c0158d /pkg
parent76a27f417fc7fd24b4fdf12f0aeeb94ecca958d3 (diff)
add various interactive rebase commands
Diffstat (limited to 'pkg')
-rw-r--r--pkg/app/app.go2
-rw-r--r--pkg/commands/git.go120
-rw-r--r--pkg/commands/os.go16
-rw-r--r--pkg/gui/commits_panel.go38
-rw-r--r--pkg/gui/keybindings.go24
-rw-r--r--pkg/gui/status_panel.go4
-rw-r--r--pkg/i18n/english.go13
7 files changed, 178 insertions, 39 deletions
diff --git a/pkg/app/app.go b/pkg/app/app.go
index e1f6654af..3b6c0fec2 100644
--- a/pkg/app/app.go
+++ b/pkg/app/app.go
@@ -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)
diff --git a/pkg/commands/git.go b/pkg/commands/git.go
index 51e943ba6..8bc1f0913 100644
--- a/pkg/commands/git.go
+++ b/pkg/commands/git.go
@@ -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
- }
- todo += a + " " + commit.Sha + "\n"
+ orderedCommits := append(commits[0:index], commits[index+1], commits[index])
+ for _, commit := range orderedCommits {
+ todo = "pick " + commit.Sha + "\n" + todo
+ }
+
+ 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
+ return cmd, nil
+}
+
+func (c *GitCommand) HardReset(baseSha string) error {
+ return c.OSCommand.RunCommand("git reset --hard " + baseSha)
+}
+
+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")
}
- out, err := cmd.CombinedOutput()
- outString := string(out)
- c.Log.Info(outString)
- if err != nil {
- if len(outString) == 0 {
- return nil, err
+ todo := ""
+ for i, commit := range commits[0 : index+1] {
+ a := "pick"
+ if i == index {
+ a = action
}
- return nil, errors.New(outString)
+ todo = a + " " + commit.Sha + "\n" + todo
}
- return nil, nil
+ return todo, nil
}
diff --git a/pkg/commands/os.go b/pkg/commands/os.go
index c404f4de1..e5ae0a041 100644
--- a/pkg/commands/os.go
+++ b/pkg/commands/os.go
@@ -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
+}
diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go
index 4bf0d4362..374f7ccf0 100644
--- a/pkg/gui/commits_panel.go
+++ b/pkg/gui/commits_panel.go
@@ -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)
+}
diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go
index df6bd8ee3..c0becfe2b 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -311,6 +311,30 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
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,
Modifier: gocui.ModNone,
diff --git a/pkg/gui/status_panel.go b/pkg/gui/status_panel.go
index 06b94d722..a6bed8f83 100644
--- a/pkg/gui/status_panel.go
+++ b/pkg/gui/status_panel.go
@@ -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
}
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index d7b509861..5fef83176 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -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{