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:
@ -121,7 +121,7 @@ func (app *App) Run() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) Rebase() 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)
|
ioutil.WriteFile(".git/rebase-merge/git-rebase-todo", []byte(os.Getenv("LAZYGIT_REBASE_TODO")), 0644)
|
||||||
|
|
||||||
|
@ -276,7 +276,7 @@ func (c *GitCommand) RebaseBranch(onto string) error {
|
|||||||
return err
|
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
|
// 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
|
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")
|
exists, err := c.OSCommand.FileExists(".git/rebase-apply")
|
||||||
if err != nil {
|
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
|
// RemoveFile directly
|
||||||
@ -537,14 +547,21 @@ func (c *GitCommand) getMergeBase() (string, error) {
|
|||||||
|
|
||||||
// GetRebasingCommits obtains the commits that we're in the process of rebasing
|
// GetRebasingCommits obtains the commits that we're in the process of rebasing
|
||||||
func (c *GitCommand) GetRebasingCommits() ([]*Commit, error) {
|
func (c *GitCommand) GetRebasingCommits() ([]*Commit, error) {
|
||||||
rebasing, err := c.IsInRebaseState()
|
rebaseMode, err := c.RebaseMode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !rebasing {
|
switch rebaseMode {
|
||||||
|
case "normal":
|
||||||
|
return c.GetNormalRebasingCommits()
|
||||||
|
case "interactive":
|
||||||
|
return c.GetInteractiveRebasingCommits()
|
||||||
|
default:
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GitCommand) GetNormalRebasingCommits() ([]*Commit, error) {
|
||||||
rewrittenCount := 0
|
rewrittenCount := 0
|
||||||
bytesContent, err := ioutil.ReadFile(".git/rebase-apply/rewritten")
|
bytesContent, err := ioutil.ReadFile(".git/rebase-apply/rewritten")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -585,6 +602,10 @@ func (c *GitCommand) GetRebasingCommits() ([]*Commit, error) {
|
|||||||
return commits, nil
|
return commits, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *GitCommand) GetInteractiveRebasingCommits() ([]*Commit, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// assuming the file starts like this:
|
// assuming the file starts like this:
|
||||||
// From e93d4193e6dd45ca9cf3a5a273d7ba6cd8b8fb20 Mon Sep 17 00:00:00 2001
|
// From e93d4193e6dd45ca9cf3a5a273d7ba6cd8b8fb20 Mon Sep 17 00:00:00 2001
|
||||||
// From: Lazygit Tester <test@example.com>
|
// From: Lazygit Tester <test@example.com>
|
||||||
@ -780,27 +801,55 @@ func (c *GitCommand) GenericMerge(commandType string, command string) error {
|
|||||||
return c.OSCommand.RunCommand(gitCommand)
|
return c.OSCommand.RunCommand(gitCommand)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GitCommand) InteractiveRebase(commits []*Commit, index int, action string) (*exec.Cmd, error) {
|
func (c *GitCommand) RewordCommit(commits []*Commit, index int) (*exec.Cmd, error) {
|
||||||
ex, err := os.Executable() // get the executable path for git to use
|
todo, err := c.GenerateGenericRebaseTodo(commits, index, "reword")
|
||||||
if err != nil {
|
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
|
return c.PrepareInteractiveRebaseCommand(commits[index+1].Sha, todo)
|
||||||
c.Log.Warn(len(commits))
|
}
|
||||||
c.Log.Warn(index)
|
|
||||||
if len(commits) <= index+1 {
|
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
|
// 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 := ""
|
todo := ""
|
||||||
for i, commit := range commits[0 : index+1] {
|
orderedCommits := append(commits[0:index], commits[index+1], commits[index])
|
||||||
a := "pick"
|
for _, commit := range orderedCommits {
|
||||||
if i == index {
|
todo = "pick " + commit.Sha + "\n" + todo
|
||||||
a = action
|
}
|
||||||
}
|
|
||||||
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"
|
debug := "FALSE"
|
||||||
@ -808,7 +857,7 @@ func (c *GitCommand) InteractiveRebase(commits []*Commit, index int, action stri
|
|||||||
debug = "TRUE"
|
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:]...)
|
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,
|
"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()
|
todo := ""
|
||||||
outString := string(out)
|
for i, commit := range commits[0 : index+1] {
|
||||||
c.Log.Info(outString)
|
a := "pick"
|
||||||
if err != nil {
|
if i == index {
|
||||||
if len(outString) == 0 {
|
a = action
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
return nil, errors.New(outString)
|
todo = a + " " + commit.Sha + "\n" + todo
|
||||||
}
|
}
|
||||||
return nil, nil
|
return todo, nil
|
||||||
}
|
}
|
||||||
|
@ -247,3 +247,19 @@ func (c *OSCommand) FileExists(path string) (bool, error) {
|
|||||||
}
|
}
|
||||||
return true, nil
|
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
|
||||||
|
}
|
||||||
|
@ -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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
}
|
}
|
||||||
if subProcess != nil {
|
if subProcess != nil {
|
||||||
gui.SubProcess = subProcess
|
gui.SubProcess = subProcess
|
||||||
// g.Update(func(g *gocui.Gui) error {
|
|
||||||
// return gui.Errors.ErrSubProcess
|
|
||||||
// })
|
|
||||||
return gui.Errors.ErrSubProcess
|
return gui.Errors.ErrSubProcess
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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)
|
||||||
|
}
|
||||||
|
@ -310,6 +310,30 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleCommitFixup,
|
Handler: gui.handleCommitFixup,
|
||||||
Description: gui.Tr.SLocalize("fixupCommit"),
|
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",
|
ViewName: "stash",
|
||||||
Key: gocui.KeySpace,
|
Key: gocui.KeySpace,
|
||||||
|
@ -94,11 +94,11 @@ func (gui *Gui) updateWorkTreeState() error {
|
|||||||
gui.State.WorkingTreeState = "merging"
|
gui.State.WorkingTreeState = "merging"
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
rebasing, err := gui.GitCommand.IsInRebaseState()
|
rebaseMode, err := gui.GitCommand.RebaseMode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if rebasing {
|
if rebaseMode != "" {
|
||||||
gui.State.WorkingTreeState = "rebasing"
|
gui.State.WorkingTreeState = "rebasing"
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -283,6 +283,19 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
ID: "renameCommit",
|
ID: "renameCommit",
|
||||||
Other: "rename commit",
|
Other: "rename commit",
|
||||||
}, &i18n.Message{
|
}, &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",
|
ID: "renameCommitEditor",
|
||||||
Other: "rename commit with editor",
|
Other: "rename commit with editor",
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
|
Reference in New Issue
Block a user