mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-01-10 04:07:18 +02:00
Implement squash, fixup, drop, and reword in terms of daemon
This commit is contained in:
parent
ab25600ccb
commit
b8fbe9756e
@ -1,13 +1,16 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/env"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// Sometimes lazygit will be invoked in daemon mode from a parent lazygit process.
|
||||
@ -34,6 +37,15 @@ const (
|
||||
// to prepend the content of `RebaseTODOEnvKey` to the default `git-rebase-todo`
|
||||
// file instead of using it as a replacement.
|
||||
PrependLinesEnvKey string = "LAZYGIT_PREPEND_LINES"
|
||||
|
||||
// If this is set, it tells lazygit to read the original todo file, and
|
||||
// change the action for one or more entries in it. The value of the variable
|
||||
// will have one or more lines of the form "Sha1:newAction", e.g.
|
||||
// a02b54e1b7e7e8dd8bc1958c11ef4ee4df459ea4:edit
|
||||
// The existing action of the todo to be changed is expected to be "pick".
|
||||
//
|
||||
// If this is used, the value of RebaseTODOEnvKey must be empty.
|
||||
ChangeTodoActionEnvKey string = "LAZYGIT_CHANGE_TODO_ACTION"
|
||||
)
|
||||
|
||||
type Daemon interface {
|
||||
@ -94,19 +106,52 @@ func (self *rebaseDaemon) Run() error {
|
||||
}
|
||||
|
||||
func (self *rebaseDaemon) writeTodoFile(path string) error {
|
||||
todoContent := []byte(os.Getenv(RebaseTODOEnvKey))
|
||||
if changeTodoActionEnvValue := os.Getenv(ChangeTodoActionEnvKey); changeTodoActionEnvValue != "" {
|
||||
return self.changeTodoAction(path, changeTodoActionEnvValue)
|
||||
} else {
|
||||
todoContent := []byte(os.Getenv(RebaseTODOEnvKey))
|
||||
|
||||
prependLines := os.Getenv(PrependLinesEnvKey) != ""
|
||||
if prependLines {
|
||||
existingContent, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
prependLines := os.Getenv(PrependLinesEnvKey) != ""
|
||||
if prependLines {
|
||||
existingContent, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
todoContent = append(todoContent, existingContent...)
|
||||
}
|
||||
|
||||
todoContent = append(todoContent, existingContent...)
|
||||
return os.WriteFile(path, todoContent, 0o644)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *rebaseDaemon) changeTodoAction(path string, changeTodoActionEnvValue string) error {
|
||||
lines := strings.Split(changeTodoActionEnvValue, "\n")
|
||||
for _, line := range lines {
|
||||
fields := strings.Split(line, ":")
|
||||
if len(fields) != 2 {
|
||||
return fmt.Errorf("Unexpected value for %s: %s", ChangeTodoActionEnvKey, changeTodoActionEnvValue)
|
||||
}
|
||||
sha, newAction := fields[0], self.actionFromString(fields[1])
|
||||
if int(newAction) == 0 {
|
||||
return fmt.Errorf("Unknown action in %s", changeTodoActionEnvValue)
|
||||
}
|
||||
if err := utils.EditRebaseTodo(path, sha, todo.Pick, newAction); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return os.WriteFile(path, todoContent, 0o644)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *rebaseDaemon) actionFromString(actionString string) todo.TodoCommand {
|
||||
for t := todo.Pick; t < todo.Comment; t++ {
|
||||
if t.String() == actionString {
|
||||
return t
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func gitDir() string {
|
||||
|
@ -55,14 +55,14 @@ func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, me
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index int) (oscommands.ICmdObj, error) {
|
||||
todo, sha, err := self.BuildSingleActionTodo(commits, index, "reword")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: sha,
|
||||
todoLines: todo,
|
||||
baseShaOrRoot: getBaseShaOrRoot(commits, index+1),
|
||||
changeTodoActions: []ChangeTodoAction{
|
||||
{
|
||||
sha: commits[index].Sha,
|
||||
newAction: todo.Reword,
|
||||
},
|
||||
},
|
||||
}), nil
|
||||
}
|
||||
|
||||
@ -114,16 +114,21 @@ func (self *RebaseCommands) MoveCommitDown(commits []*models.Commit, index int)
|
||||
}).Run()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index int, action string) error {
|
||||
todo, sha, err := self.BuildSingleActionTodo(commits, index, action)
|
||||
if err != nil {
|
||||
return err
|
||||
func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index int, action todo.TodoCommand) error {
|
||||
baseIndex := index + 1
|
||||
if action == todo.Squash || action == todo.Fixup {
|
||||
baseIndex++
|
||||
}
|
||||
|
||||
baseShaOrRoot := getBaseShaOrRoot(commits, baseIndex)
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: sha,
|
||||
todoLines: todo,
|
||||
baseShaOrRoot: baseShaOrRoot,
|
||||
overrideEditor: true,
|
||||
changeTodoActions: []ChangeTodoAction{{
|
||||
sha: commits[index].Sha,
|
||||
newAction: action,
|
||||
}},
|
||||
}).Run()
|
||||
}
|
||||
|
||||
@ -136,11 +141,17 @@ func (self *RebaseCommands) EditRebase(branchRef string) error {
|
||||
}).Run()
|
||||
}
|
||||
|
||||
type ChangeTodoAction struct {
|
||||
sha string
|
||||
newAction todo.TodoCommand
|
||||
}
|
||||
|
||||
type PrepareInteractiveRebaseCommandOpts struct {
|
||||
baseShaOrRoot string
|
||||
todoLines []TodoLine
|
||||
overrideEditor bool
|
||||
prepend bool
|
||||
baseShaOrRoot string
|
||||
todoLines []TodoLine
|
||||
overrideEditor bool
|
||||
prepend bool
|
||||
changeTodoActions []ChangeTodoAction
|
||||
}
|
||||
|
||||
// PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase
|
||||
@ -159,6 +170,14 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteract
|
||||
debug = "TRUE"
|
||||
}
|
||||
|
||||
changeTodoValue := strings.Join(slices.Map(opts.changeTodoActions, func(c ChangeTodoAction) string {
|
||||
return fmt.Sprintf("%s:%s", c.sha, c.newAction)
|
||||
}), "\n")
|
||||
|
||||
if todo != "" && changeTodoValue != "" {
|
||||
panic("It's not allowed to pass both todoLines and changeActionOpts")
|
||||
}
|
||||
|
||||
rebaseMergesArg := " --rebase-merges"
|
||||
if self.version.IsOlderThan(2, 22, 0) {
|
||||
rebaseMergesArg = ""
|
||||
@ -170,16 +189,19 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteract
|
||||
cmdObj := self.cmd.New(cmdStr)
|
||||
|
||||
gitSequenceEditor := ex
|
||||
if todo == "" {
|
||||
gitSequenceEditor = "true"
|
||||
} else {
|
||||
if todo != "" {
|
||||
self.os.LogCommand(fmt.Sprintf("Creating TODO file for interactive rebase: \n\n%s", todo), false)
|
||||
} else if changeTodoValue != "" {
|
||||
self.os.LogCommand(fmt.Sprintf("Changing TODO action: %s", changeTodoValue), false)
|
||||
} else {
|
||||
gitSequenceEditor = "true"
|
||||
}
|
||||
|
||||
cmdObj.AddEnvVars(
|
||||
daemon.DaemonKindEnvKey+"="+string(daemon.InteractiveRebase),
|
||||
daemon.RebaseTODOEnvKey+"="+todo,
|
||||
daemon.PrependLinesEnvKey+"="+prependLines,
|
||||
daemon.ChangeTodoActionEnvKey+"="+changeTodoValue,
|
||||
"DEBUG="+debug,
|
||||
"LANG=en_US.UTF-8", // Force using EN as language
|
||||
"LC_ALL=en_US.UTF-8", // Force using EN as language
|
||||
@ -193,33 +215,6 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteract
|
||||
return cmdObj
|
||||
}
|
||||
|
||||
// produces TodoLines where every commit is picked (or dropped for merge commits) except for the commit at the given index, which
|
||||
// will have the given action applied to it.
|
||||
func (self *RebaseCommands) BuildSingleActionTodo(commits []*models.Commit, actionIndex int, action string) ([]TodoLine, string, error) {
|
||||
baseIndex := actionIndex + 1
|
||||
|
||||
if action == "squash" || action == "fixup" {
|
||||
baseIndex++
|
||||
}
|
||||
|
||||
todoLines := self.BuildTodoLines(commits[0:baseIndex], func(commit *models.Commit, i int) string {
|
||||
if i == actionIndex {
|
||||
return action
|
||||
} else if commit.IsMerge() {
|
||||
// your typical interactive rebase will actually drop merge commits by default. Damn git CLI, you scary!
|
||||
// doing this means we don't need to worry about rebasing over merges which always causes problems.
|
||||
// you typically shouldn't be doing rebases that pass over merge commits anyway.
|
||||
return "drop"
|
||||
} else {
|
||||
return "pick"
|
||||
}
|
||||
})
|
||||
|
||||
baseShaOrRoot := getBaseShaOrRoot(commits, baseIndex)
|
||||
|
||||
return todoLines, baseShaOrRoot, nil
|
||||
}
|
||||
|
||||
// AmendTo amends the given commit with whatever files are staged
|
||||
func (self *RebaseCommands) AmendTo(commit *models.Commit) error {
|
||||
if err := self.commit.CreateFixupCommit(commit.Sha); err != nil {
|
||||
@ -278,15 +273,13 @@ func (self *RebaseCommands) BeginInteractiveRebaseForCommit(commits []*models.Co
|
||||
return errors.New(self.Tr.DisabledForGPG)
|
||||
}
|
||||
|
||||
todo, sha, err := self.BuildSingleActionTodo(commits, commitIndex, "edit")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: sha,
|
||||
todoLines: todo,
|
||||
baseShaOrRoot: getBaseShaOrRoot(commits, commitIndex+1),
|
||||
overrideEditor: true,
|
||||
changeTodoActions: []ChangeTodoAction{{
|
||||
sha: commits[commitIndex].Sha,
|
||||
newAction: todo.Edit,
|
||||
}},
|
||||
}).Run()
|
||||
}
|
||||
|
||||
|
@ -169,7 +169,7 @@ func (self *LocalCommitsController) squashDown(commit *models.Commit) error {
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.SquashCommitDown)
|
||||
return self.interactiveRebase("squash")
|
||||
return self.interactiveRebase(todo.Squash)
|
||||
})
|
||||
},
|
||||
})
|
||||
@ -194,7 +194,7 @@ func (self *LocalCommitsController) fixup(commit *models.Commit) error {
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.FixupCommit)
|
||||
return self.interactiveRebase("fixup")
|
||||
return self.interactiveRebase(todo.Fixup)
|
||||
})
|
||||
},
|
||||
})
|
||||
@ -284,7 +284,7 @@ func (self *LocalCommitsController) drop(commit *models.Commit) error {
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DropCommit)
|
||||
return self.interactiveRebase("drop")
|
||||
return self.interactiveRebase(todo.Drop)
|
||||
})
|
||||
},
|
||||
})
|
||||
@ -320,7 +320,7 @@ func (self *LocalCommitsController) pick(commit *models.Commit) error {
|
||||
return self.pullFiles()
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) interactiveRebase(action string) error {
|
||||
func (self *LocalCommitsController) interactiveRebase(action todo.TodoCommand) error {
|
||||
err := self.git.Rebase.InteractiveRebase(self.model.Commits, self.context().GetSelectedLineIdx(), action)
|
||||
return self.helpers.MergeAndRebase.CheckMergeOrRebase(err)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user