mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-06-15 00:15:32 +02:00
Refactor to tighten interface to lazygit daemon
This commit is contained in:
committed by
Stefan Haller
parent
a8586ba57e
commit
185bbf0c75
@ -2,15 +2,16 @@ package daemon
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/fsmiamoto/git-todo-parser/todo"
|
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
"github.com/jesseduffield/lazygit/pkg/common"
|
"github.com/jesseduffield/lazygit/pkg/common"
|
||||||
"github.com/jesseduffield/lazygit/pkg/env"
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Sometimes lazygit will be invoked in daemon mode from a parent lazygit process.
|
// Sometimes lazygit will be invoked in daemon mode from a parent lazygit process.
|
||||||
@ -18,58 +19,56 @@ import (
|
|||||||
// For example, if we want to ensure that a git command doesn't hang due to
|
// For example, if we want to ensure that a git command doesn't hang due to
|
||||||
// waiting for an editor to save a commit message, we can tell git to invoke lazygit
|
// waiting for an editor to save a commit message, we can tell git to invoke lazygit
|
||||||
// as the editor via 'GIT_EDITOR=lazygit', and use the env var
|
// as the editor via 'GIT_EDITOR=lazygit', and use the env var
|
||||||
// 'LAZYGIT_DAEMON_KIND=EXIT_IMMEDIATELY' to specify that we want to run lazygit
|
// 'LAZYGIT_DAEMON_KIND=1' (exit immediately) to specify that we want to run lazygit
|
||||||
// as a daemon which simply exits immediately. Any additional arguments we want
|
// as a daemon which simply exits immediately.
|
||||||
// to pass to a daemon can be done via other env vars.
|
//
|
||||||
|
// 'Daemon' is not the best name for this, because it's not a persistent background
|
||||||
|
// process, but it's close enough.
|
||||||
|
|
||||||
type DaemonKind string
|
type DaemonKind int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
InteractiveRebase DaemonKind = "INTERACTIVE_REBASE"
|
// for when we fail to parse the daemon kind
|
||||||
ExitImmediately DaemonKind = "EXIT_IMMEDIATELY"
|
DaemonKindUnknown DaemonKind = iota
|
||||||
|
|
||||||
|
DaemonKindExitImmediately
|
||||||
|
DaemonKindCherryPick
|
||||||
|
DaemonKindMoveTodoUp
|
||||||
|
DaemonKindMoveTodoDown
|
||||||
|
DaemonKindInsertBreak
|
||||||
|
DaemonKindChangeTodoActions
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DaemonKindEnvKey string = "LAZYGIT_DAEMON_KIND"
|
DaemonKindEnvKey string = "LAZYGIT_DAEMON_KIND"
|
||||||
|
|
||||||
// Contains a json-encoded instance of the InteractiveRebaseInstructions struct
|
// Contains json-encoded arguments to the daemon
|
||||||
InteractiveRebaseInstructionsEnvKey string = "LAZYGIT_DAEMON_INSTRUCTIONS"
|
DaemonInstructionEnvKey string = "LAZYGIT_DAEMON_INSTRUCTION"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Exactly one of the fields in this struct is expected to be non-empty
|
func getInstruction() Instruction {
|
||||||
type InteractiveRebaseInstructions struct {
|
jsonData := os.Getenv(DaemonInstructionEnvKey)
|
||||||
// If this is non-empty, this string is prepended to the git-rebase-todo
|
|
||||||
// file. The string is expected to have newlines at the end of each line.
|
|
||||||
LinesToPrependToRebaseTODO string
|
|
||||||
|
|
||||||
// If this is non-empty, it tells lazygit to read the original todo file, and
|
mapping := map[DaemonKind]func(string) Instruction{
|
||||||
// change the action for one or more entries in it.
|
DaemonKindExitImmediately: deserializeInstruction[*ExitImmediatelyInstruction],
|
||||||
// The existing action of the todo to be changed is expected to be "pick".
|
DaemonKindCherryPick: deserializeInstruction[*CherryPickCommitsInstruction],
|
||||||
ChangeTodoActions []ChangeTodoAction
|
DaemonKindChangeTodoActions: deserializeInstruction[*ChangeTodoActionsInstruction],
|
||||||
|
DaemonKindMoveTodoUp: deserializeInstruction[*MoveTodoUpInstruction],
|
||||||
|
DaemonKindMoveTodoDown: deserializeInstruction[*MoveTodoDownInstruction],
|
||||||
|
DaemonKindInsertBreak: deserializeInstruction[*InsertBreakInstruction],
|
||||||
|
}
|
||||||
|
|
||||||
// Can be set to the sha of a "pick" todo that will be moved down by one.
|
return mapping[getDaemonKind()](jsonData)
|
||||||
ShaToMoveDown string
|
|
||||||
|
|
||||||
// Can be set to the sha of a "pick" todo that will be moved up by one.
|
|
||||||
ShaToMoveUp string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChangeTodoAction struct {
|
|
||||||
Sha string
|
|
||||||
NewAction todo.TodoCommand
|
|
||||||
}
|
|
||||||
|
|
||||||
type Daemon interface {
|
|
||||||
Run() error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Handle(common *common.Common) {
|
func Handle(common *common.Common) {
|
||||||
d := getDaemon(common)
|
if !InDaemonMode() {
|
||||||
if d == nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := d.Run(); err != nil {
|
instruction := getInstruction()
|
||||||
|
|
||||||
|
if err := instruction.run(common); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,89 +76,200 @@ func Handle(common *common.Common) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func InDaemonMode() bool {
|
func InDaemonMode() bool {
|
||||||
return getDaemonKind() != ""
|
return getDaemonKind() != DaemonKindUnknown
|
||||||
}
|
|
||||||
|
|
||||||
func getDaemon(common *common.Common) Daemon {
|
|
||||||
switch getDaemonKind() {
|
|
||||||
case InteractiveRebase:
|
|
||||||
return &rebaseDaemon{c: common}
|
|
||||||
case ExitImmediately:
|
|
||||||
return &exitImmediatelyDaemon{c: common}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDaemonKind() DaemonKind {
|
func getDaemonKind() DaemonKind {
|
||||||
return DaemonKind(os.Getenv(DaemonKindEnvKey))
|
intValue, err := strconv.Atoi(os.Getenv(DaemonKindEnvKey))
|
||||||
}
|
|
||||||
|
|
||||||
type rebaseDaemon struct {
|
|
||||||
c *common.Common
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *rebaseDaemon) Run() error {
|
|
||||||
self.c.Log.Info("Lazygit invoked as interactive rebase demon")
|
|
||||||
self.c.Log.Info("args: ", os.Args)
|
|
||||||
path := os.Args[1]
|
|
||||||
|
|
||||||
if strings.HasSuffix(path, "git-rebase-todo") {
|
|
||||||
return self.writeTodoFile(path)
|
|
||||||
} else if strings.HasSuffix(path, filepath.Join(gitDir(), "COMMIT_EDITMSG")) { // TODO: test
|
|
||||||
// if we are rebasing and squashing, we'll see a COMMIT_EDITMSG
|
|
||||||
// but in this case we don't need to edit it, so we'll just return
|
|
||||||
} else {
|
|
||||||
self.c.Log.Info("Lazygit demon did not match on any use cases")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *rebaseDaemon) writeTodoFile(path string) error {
|
|
||||||
jsonData := os.Getenv(InteractiveRebaseInstructionsEnvKey)
|
|
||||||
instructions := InteractiveRebaseInstructions{}
|
|
||||||
err := json.Unmarshal([]byte(jsonData), &instructions)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return DaemonKindUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
if instructions.LinesToPrependToRebaseTODO != "" {
|
return DaemonKind(intValue)
|
||||||
return utils.PrependStrToTodoFile(path, []byte(instructions.LinesToPrependToRebaseTODO))
|
}
|
||||||
} else if len(instructions.ChangeTodoActions) != 0 {
|
|
||||||
return self.changeTodoAction(path, instructions.ChangeTodoActions)
|
// An Instruction is a command to be run by lazygit in daemon mode.
|
||||||
} else if instructions.ShaToMoveDown != "" {
|
// It is serialized to json and passed to lazygit via environment variables
|
||||||
return utils.MoveTodoDown(path, instructions.ShaToMoveDown, todo.Pick)
|
type Instruction interface {
|
||||||
} else if instructions.ShaToMoveUp != "" {
|
Kind() DaemonKind
|
||||||
return utils.MoveTodoUp(path, instructions.ShaToMoveUp, todo.Pick)
|
SerializedInstructions() string
|
||||||
|
|
||||||
|
// runs the instruction
|
||||||
|
run(common *common.Common) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func serializeInstruction[T any](instruction T) string {
|
||||||
|
jsonData, err := json.Marshal(instruction)
|
||||||
|
if err != nil {
|
||||||
|
// this should never happen
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.c.Log.Error("No instructions were given to daemon")
|
return string(jsonData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deserializeInstruction[T Instruction](jsonData string) Instruction {
|
||||||
|
var instruction T
|
||||||
|
err := json.Unmarshal([]byte(jsonData), &instruction)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return instruction
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToEnvVars(instruction Instruction) []string {
|
||||||
|
return []string{
|
||||||
|
fmt.Sprintf("%s=%d", DaemonKindEnvKey, instruction.Kind()),
|
||||||
|
fmt.Sprintf("%s=%s", DaemonInstructionEnvKey, instruction.SerializedInstructions()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExitImmediatelyInstruction struct{}
|
||||||
|
|
||||||
|
func (self *ExitImmediatelyInstruction) Kind() DaemonKind {
|
||||||
|
return DaemonKindExitImmediately
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *ExitImmediatelyInstruction) SerializedInstructions() string {
|
||||||
|
return serializeInstruction(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *ExitImmediatelyInstruction) run(common *common.Common) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *rebaseDaemon) changeTodoAction(path string, changeTodoActions []ChangeTodoAction) error {
|
func NewExitImmediatelyInstruction() Instruction {
|
||||||
for _, c := range changeTodoActions {
|
return &ExitImmediatelyInstruction{}
|
||||||
if err := utils.EditRebaseTodo(path, c.Sha, todo.Pick, c.NewAction); err != nil {
|
}
|
||||||
return err
|
|
||||||
|
type CherryPickCommitsInstruction struct {
|
||||||
|
Todo string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCherryPickCommitsInstruction(commits []*models.Commit) Instruction {
|
||||||
|
todoLines := lo.Map(commits, func(commit *models.Commit, _ int) TodoLine {
|
||||||
|
return TodoLine{
|
||||||
|
Action: "pick",
|
||||||
|
Commit: commit,
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
todo := TodoLinesToString(todoLines)
|
||||||
|
|
||||||
|
return &CherryPickCommitsInstruction{
|
||||||
|
Todo: todo,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func gitDir() string {
|
func (self *CherryPickCommitsInstruction) Kind() DaemonKind {
|
||||||
dir := env.GetGitDirEnv()
|
return DaemonKindCherryPick
|
||||||
if dir == "" {
|
}
|
||||||
return ".git"
|
|
||||||
|
func (self *CherryPickCommitsInstruction) SerializedInstructions() string {
|
||||||
|
return serializeInstruction(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *CherryPickCommitsInstruction) run(common *common.Common) error {
|
||||||
|
return handleInteractiveRebase(common, func(path string) error {
|
||||||
|
return utils.PrependStrToTodoFile(path, []byte(self.Todo))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChangeTodoActionsInstruction struct {
|
||||||
|
Changes []ChangeTodoAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChangeTodoActionsInstruction(changes []ChangeTodoAction) Instruction {
|
||||||
|
return &ChangeTodoActionsInstruction{
|
||||||
|
Changes: changes,
|
||||||
}
|
}
|
||||||
return dir
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type exitImmediatelyDaemon struct {
|
func (self *ChangeTodoActionsInstruction) Kind() DaemonKind {
|
||||||
c *common.Common
|
return DaemonKindChangeTodoActions
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *exitImmediatelyDaemon) Run() error {
|
func (self *ChangeTodoActionsInstruction) SerializedInstructions() string {
|
||||||
return nil
|
return serializeInstruction(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *ChangeTodoActionsInstruction) run(common *common.Common) error {
|
||||||
|
return handleInteractiveRebase(common, func(path string) error {
|
||||||
|
for _, c := range self.Changes {
|
||||||
|
if err := utils.EditRebaseTodo(path, c.Sha, todo.Pick, c.NewAction); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type MoveTodoUpInstruction struct {
|
||||||
|
Sha string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMoveTodoUpInstruction(sha string) Instruction {
|
||||||
|
return &MoveTodoUpInstruction{
|
||||||
|
Sha: sha,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MoveTodoUpInstruction) Kind() DaemonKind {
|
||||||
|
return DaemonKindMoveTodoUp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MoveTodoUpInstruction) SerializedInstructions() string {
|
||||||
|
return serializeInstruction(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MoveTodoUpInstruction) run(common *common.Common) error {
|
||||||
|
return handleInteractiveRebase(common, func(path string) error {
|
||||||
|
return utils.MoveTodoUp(path, self.Sha, todo.Pick)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type MoveTodoDownInstruction struct {
|
||||||
|
Sha string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMoveTodoDownInstruction(sha string) Instruction {
|
||||||
|
return &MoveTodoDownInstruction{
|
||||||
|
Sha: sha,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MoveTodoDownInstruction) Kind() DaemonKind {
|
||||||
|
return DaemonKindMoveTodoDown
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MoveTodoDownInstruction) SerializedInstructions() string {
|
||||||
|
return serializeInstruction(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MoveTodoDownInstruction) run(common *common.Common) error {
|
||||||
|
return handleInteractiveRebase(common, func(path string) error {
|
||||||
|
return utils.MoveTodoDown(path, self.Sha, todo.Pick)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type InsertBreakInstruction struct{}
|
||||||
|
|
||||||
|
func NewInsertBreakInstruction() Instruction {
|
||||||
|
return &InsertBreakInstruction{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *InsertBreakInstruction) Kind() DaemonKind {
|
||||||
|
return DaemonKindInsertBreak
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *InsertBreakInstruction) SerializedInstructions() string {
|
||||||
|
return serializeInstruction(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *InsertBreakInstruction) run(common *common.Common) error {
|
||||||
|
return handleInteractiveRebase(common, func(path string) error {
|
||||||
|
return utils.PrependStrToTodoFile(path, []byte("break\n"))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
64
pkg/app/daemon/rebase.go
Normal file
64
pkg/app/daemon/rebase.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package daemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||||
|
"github.com/jesseduffield/generics/slices"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/common"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/env"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TodoLine struct {
|
||||||
|
Action string
|
||||||
|
Commit *models.Commit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *TodoLine) ToString() string {
|
||||||
|
if self.Action == "break" {
|
||||||
|
return self.Action + "\n"
|
||||||
|
} else {
|
||||||
|
return self.Action + " " + self.Commit.Sha + " " + self.Commit.Name + "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TodoLinesToString(todoLines []TodoLine) string {
|
||||||
|
lines := slices.Map(todoLines, func(todoLine TodoLine) string {
|
||||||
|
return todoLine.ToString()
|
||||||
|
})
|
||||||
|
|
||||||
|
return strings.Join(slices.Reverse(lines), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChangeTodoAction struct {
|
||||||
|
Sha string
|
||||||
|
NewAction todo.TodoCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleInteractiveRebase(common *common.Common, f func(path string) error) error {
|
||||||
|
common.Log.Info("Lazygit invoked as interactive rebase demon")
|
||||||
|
common.Log.Info("args: ", os.Args)
|
||||||
|
path := os.Args[1]
|
||||||
|
|
||||||
|
if strings.HasSuffix(path, "git-rebase-todo") {
|
||||||
|
return f(path)
|
||||||
|
} else if strings.HasSuffix(path, filepath.Join(gitDir(), "COMMIT_EDITMSG")) { // TODO: test
|
||||||
|
// if we are rebasing and squashing, we'll see a COMMIT_EDITMSG
|
||||||
|
// but in this case we don't need to edit it, so we'll just return
|
||||||
|
} else {
|
||||||
|
common.Log.Info("Lazygit demon did not match on any use cases")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitDir() string {
|
||||||
|
dir := env.GetGitDirEnv()
|
||||||
|
if dir == "" {
|
||||||
|
return ".git"
|
||||||
|
}
|
||||||
|
return dir
|
||||||
|
}
|
@ -105,15 +105,16 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
|
|||||||
|
|
||||||
baseIndex := sourceCommitIdx + 1
|
baseIndex := sourceCommitIdx + 1
|
||||||
|
|
||||||
|
changes := []daemon.ChangeTodoAction{
|
||||||
|
{Sha: commits[sourceCommitIdx].Sha, NewAction: todo.Edit},
|
||||||
|
{Sha: commits[destinationCommitIdx].Sha, NewAction: todo.Edit},
|
||||||
|
}
|
||||||
|
self.os.LogCommand(logTodoChanges(changes), false)
|
||||||
|
|
||||||
err := self.rebase.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
err := self.rebase.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||||
baseShaOrRoot: commits[baseIndex].Sha,
|
baseShaOrRoot: commits[baseIndex].Sha,
|
||||||
overrideEditor: true,
|
overrideEditor: true,
|
||||||
instruction: ChangeTodoActionsInstruction{
|
instruction: daemon.NewChangeTodoActionsInstruction(changes),
|
||||||
actions: []daemon.ChangeTodoAction{
|
|
||||||
{Sha: commits[sourceCommitIdx].Sha, NewAction: todo.Edit},
|
|
||||||
{Sha: commits[destinationCommitIdx].Sha, NewAction: todo.Edit},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).Run()
|
}).Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package git_commands
|
package git_commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@ -13,6 +12,7 @@ import (
|
|||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RebaseCommands struct {
|
type RebaseCommands struct {
|
||||||
@ -56,14 +56,15 @@ func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, me
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index int) (oscommands.ICmdObj, error) {
|
func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index int) (oscommands.ICmdObj, error) {
|
||||||
|
changes := []daemon.ChangeTodoAction{{
|
||||||
|
Sha: commits[index].Sha,
|
||||||
|
NewAction: todo.Reword,
|
||||||
|
}}
|
||||||
|
self.os.LogCommand(logTodoChanges(changes), false)
|
||||||
|
|
||||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||||
baseShaOrRoot: getBaseShaOrRoot(commits, index+1),
|
baseShaOrRoot: getBaseShaOrRoot(commits, index+1),
|
||||||
instruction: ChangeTodoActionsInstruction{
|
instruction: daemon.NewChangeTodoActionsInstruction(changes),
|
||||||
actions: []daemon.ChangeTodoAction{{
|
|
||||||
Sha: commits[index].Sha,
|
|
||||||
NewAction: todo.Reword,
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,9 +103,13 @@ func (self *RebaseCommands) GenericAmend(commits []*models.Commit, index int, f
|
|||||||
func (self *RebaseCommands) MoveCommitDown(commits []*models.Commit, index int) error {
|
func (self *RebaseCommands) MoveCommitDown(commits []*models.Commit, index int) error {
|
||||||
baseShaOrRoot := getBaseShaOrRoot(commits, index+2)
|
baseShaOrRoot := getBaseShaOrRoot(commits, index+2)
|
||||||
|
|
||||||
|
sha := commits[index].Sha
|
||||||
|
|
||||||
|
self.os.LogCommand(fmt.Sprintf("Moving TODO down: %s", utils.ShortSha(sha)), false)
|
||||||
|
|
||||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||||
baseShaOrRoot: baseShaOrRoot,
|
baseShaOrRoot: baseShaOrRoot,
|
||||||
instruction: MoveDownInstruction{sha: commits[index].Sha},
|
instruction: daemon.NewMoveTodoDownInstruction(sha),
|
||||||
overrideEditor: true,
|
overrideEditor: true,
|
||||||
}).Run()
|
}).Run()
|
||||||
}
|
}
|
||||||
@ -112,9 +117,13 @@ func (self *RebaseCommands) MoveCommitDown(commits []*models.Commit, index int)
|
|||||||
func (self *RebaseCommands) MoveCommitUp(commits []*models.Commit, index int) error {
|
func (self *RebaseCommands) MoveCommitUp(commits []*models.Commit, index int) error {
|
||||||
baseShaOrRoot := getBaseShaOrRoot(commits, index+1)
|
baseShaOrRoot := getBaseShaOrRoot(commits, index+1)
|
||||||
|
|
||||||
|
sha := commits[index].Sha
|
||||||
|
|
||||||
|
self.os.LogCommand(fmt.Sprintf("Moving TODO up: %s", utils.ShortSha(sha)), false)
|
||||||
|
|
||||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||||
baseShaOrRoot: baseShaOrRoot,
|
baseShaOrRoot: baseShaOrRoot,
|
||||||
instruction: MoveUpInstruction{sha: commits[index].Sha},
|
instruction: daemon.NewMoveTodoUpInstruction(sha),
|
||||||
overrideEditor: true,
|
overrideEditor: true,
|
||||||
}).Run()
|
}).Run()
|
||||||
}
|
}
|
||||||
@ -127,74 +136,37 @@ func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index in
|
|||||||
|
|
||||||
baseShaOrRoot := getBaseShaOrRoot(commits, baseIndex)
|
baseShaOrRoot := getBaseShaOrRoot(commits, baseIndex)
|
||||||
|
|
||||||
|
changes := []daemon.ChangeTodoAction{{
|
||||||
|
Sha: commits[index].Sha,
|
||||||
|
NewAction: action,
|
||||||
|
}}
|
||||||
|
self.os.LogCommand(logTodoChanges(changes), false)
|
||||||
|
|
||||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||||
baseShaOrRoot: baseShaOrRoot,
|
baseShaOrRoot: baseShaOrRoot,
|
||||||
instruction: ChangeTodoActionsInstruction{
|
|
||||||
actions: []daemon.ChangeTodoAction{{
|
|
||||||
Sha: commits[index].Sha,
|
|
||||||
NewAction: action,
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
overrideEditor: true,
|
overrideEditor: true,
|
||||||
|
instruction: daemon.NewChangeTodoActionsInstruction(changes),
|
||||||
}).Run()
|
}).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *RebaseCommands) EditRebase(branchRef string) error {
|
func (self *RebaseCommands) EditRebase(branchRef string) error {
|
||||||
commands := []TodoLine{{Action: "break"}}
|
self.os.LogCommand(fmt.Sprintf("Beginning interactive rebase at '%s'", branchRef), false)
|
||||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||||
baseShaOrRoot: branchRef,
|
baseShaOrRoot: branchRef,
|
||||||
instruction: PrependLinesInstruction{todoLines: commands},
|
instruction: daemon.NewInsertBreakInstruction(),
|
||||||
}).Run()
|
}).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
type InteractiveRebaseInstruction interface {
|
func logTodoChanges(changes []daemon.ChangeTodoAction) string {
|
||||||
// Add our data to the instructions struct, and return a log string
|
changeTodoStr := strings.Join(slices.Map(changes, func(c daemon.ChangeTodoAction) string {
|
||||||
serialize(instructions *daemon.InteractiveRebaseInstructions) string
|
|
||||||
}
|
|
||||||
|
|
||||||
type PrependLinesInstruction struct {
|
|
||||||
todoLines []TodoLine
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self PrependLinesInstruction) serialize(instructions *daemon.InteractiveRebaseInstructions) string {
|
|
||||||
todoStr := TodoLinesToString(self.todoLines)
|
|
||||||
instructions.LinesToPrependToRebaseTODO = todoStr
|
|
||||||
return fmt.Sprintf("Creating TODO file for interactive rebase: \n\n%s", todoStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChangeTodoActionsInstruction struct {
|
|
||||||
actions []daemon.ChangeTodoAction
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self ChangeTodoActionsInstruction) serialize(instructions *daemon.InteractiveRebaseInstructions) string {
|
|
||||||
instructions.ChangeTodoActions = self.actions
|
|
||||||
changeTodoStr := strings.Join(slices.Map(self.actions, func(c daemon.ChangeTodoAction) string {
|
|
||||||
return fmt.Sprintf("%s:%s", c.Sha, c.NewAction)
|
return fmt.Sprintf("%s:%s", c.Sha, c.NewAction)
|
||||||
}), "\n")
|
}), "\n")
|
||||||
return fmt.Sprintf("Changing TODO actions: %s", changeTodoStr)
|
return fmt.Sprintf("Changing TODO actions: %s", changeTodoStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
type MoveDownInstruction struct {
|
|
||||||
sha string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self MoveDownInstruction) serialize(instructions *daemon.InteractiveRebaseInstructions) string {
|
|
||||||
instructions.ShaToMoveDown = self.sha
|
|
||||||
return fmt.Sprintf("Moving TODO down: %s", self.sha)
|
|
||||||
}
|
|
||||||
|
|
||||||
type MoveUpInstruction struct {
|
|
||||||
sha string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self MoveUpInstruction) serialize(instructions *daemon.InteractiveRebaseInstructions) string {
|
|
||||||
instructions.ShaToMoveUp = self.sha
|
|
||||||
return fmt.Sprintf("Moving TODO up: %s", self.sha)
|
|
||||||
}
|
|
||||||
|
|
||||||
type PrepareInteractiveRebaseCommandOpts struct {
|
type PrepareInteractiveRebaseCommandOpts struct {
|
||||||
baseShaOrRoot string
|
baseShaOrRoot string
|
||||||
instruction InteractiveRebaseInstruction
|
instruction daemon.Instruction
|
||||||
overrideEditor bool
|
overrideEditor bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,22 +194,12 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteract
|
|||||||
gitSequenceEditor := ex
|
gitSequenceEditor := ex
|
||||||
|
|
||||||
if opts.instruction != nil {
|
if opts.instruction != nil {
|
||||||
instructions := daemon.InteractiveRebaseInstructions{}
|
cmdObj.AddEnvVars(daemon.ToEnvVars(opts.instruction)...)
|
||||||
logStr := opts.instruction.serialize(&instructions)
|
|
||||||
jsonData, err := json.Marshal(instructions)
|
|
||||||
if err == nil {
|
|
||||||
envVar := fmt.Sprintf("%s=%s", daemon.InteractiveRebaseInstructionsEnvKey, jsonData)
|
|
||||||
cmdObj.AddEnvVars(envVar)
|
|
||||||
self.os.LogCommand(logStr, false)
|
|
||||||
} else {
|
|
||||||
self.Log.Error(err)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
gitSequenceEditor = "true"
|
gitSequenceEditor = "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdObj.AddEnvVars(
|
cmdObj.AddEnvVars(
|
||||||
daemon.DaemonKindEnvKey+"="+string(daemon.InteractiveRebase),
|
|
||||||
"DEBUG="+debug,
|
"DEBUG="+debug,
|
||||||
"LANG=en_US.UTF-8", // Force using EN as language
|
"LANG=en_US.UTF-8", // Force using EN as language
|
||||||
"LC_ALL=en_US.UTF-8", // Force using EN as language
|
"LC_ALL=en_US.UTF-8", // Force using EN as language
|
||||||
@ -309,15 +271,16 @@ func (self *RebaseCommands) BeginInteractiveRebaseForCommit(commits []*models.Co
|
|||||||
return errors.New(self.Tr.DisabledForGPG)
|
return errors.New(self.Tr.DisabledForGPG)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changes := []daemon.ChangeTodoAction{{
|
||||||
|
Sha: commits[commitIndex].Sha,
|
||||||
|
NewAction: todo.Edit,
|
||||||
|
}}
|
||||||
|
self.os.LogCommand(logTodoChanges(changes), false)
|
||||||
|
|
||||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||||
baseShaOrRoot: getBaseShaOrRoot(commits, commitIndex+1),
|
baseShaOrRoot: getBaseShaOrRoot(commits, commitIndex+1),
|
||||||
overrideEditor: true,
|
overrideEditor: true,
|
||||||
instruction: ChangeTodoActionsInstruction{
|
instruction: daemon.NewChangeTodoActionsInstruction(changes),
|
||||||
actions: []daemon.ChangeTodoAction{{
|
|
||||||
Sha: commits[commitIndex].Sha,
|
|
||||||
NewAction: todo.Edit,
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}).Run()
|
}).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,15 +327,16 @@ func (self *RebaseCommands) GenericMergeOrRebaseAction(commandType string, comma
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *RebaseCommands) runSkipEditorCommand(cmdObj oscommands.ICmdObj) error {
|
func (self *RebaseCommands) runSkipEditorCommand(cmdObj oscommands.ICmdObj) error {
|
||||||
|
instruction := daemon.NewExitImmediatelyInstruction()
|
||||||
lazyGitPath := oscommands.GetLazygitPath()
|
lazyGitPath := oscommands.GetLazygitPath()
|
||||||
return cmdObj.
|
return cmdObj.
|
||||||
AddEnvVars(
|
AddEnvVars(
|
||||||
daemon.DaemonKindEnvKey+"="+string(daemon.ExitImmediately),
|
|
||||||
"GIT_EDITOR="+lazyGitPath,
|
"GIT_EDITOR="+lazyGitPath,
|
||||||
"GIT_SEQUENCE_EDITOR="+lazyGitPath,
|
"GIT_SEQUENCE_EDITOR="+lazyGitPath,
|
||||||
"EDITOR="+lazyGitPath,
|
"EDITOR="+lazyGitPath,
|
||||||
"VISUAL="+lazyGitPath,
|
"VISUAL="+lazyGitPath,
|
||||||
).
|
).
|
||||||
|
AddEnvVars(daemon.ToEnvVars(instruction)...).
|
||||||
Run()
|
Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,49 +370,17 @@ func (self *RebaseCommands) DiscardOldFileChanges(commits []*models.Commit, comm
|
|||||||
|
|
||||||
// CherryPickCommits begins an interactive rebase with the given shas being cherry picked onto HEAD
|
// CherryPickCommits begins an interactive rebase with the given shas being cherry picked onto HEAD
|
||||||
func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error {
|
func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error {
|
||||||
todoLines := self.BuildTodoLinesSingleAction(commits, "pick")
|
commitLines := lo.Map(commits, func(commit *models.Commit, _ int) string {
|
||||||
|
return fmt.Sprintf("%s %s", utils.ShortSha(commit.Sha), commit.Name)
|
||||||
|
})
|
||||||
|
self.os.LogCommand(fmt.Sprintf("Cherry-picking commits:\n%s", strings.Join(commitLines, "\n")), false)
|
||||||
|
|
||||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||||
baseShaOrRoot: "HEAD",
|
baseShaOrRoot: "HEAD",
|
||||||
instruction: PrependLinesInstruction{
|
instruction: daemon.NewCherryPickCommitsInstruction(commits),
|
||||||
todoLines: todoLines,
|
|
||||||
},
|
|
||||||
}).Run()
|
}).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TodoLinesToString(todoLines []TodoLine) string {
|
|
||||||
lines := slices.Map(todoLines, func(todoLine TodoLine) string {
|
|
||||||
return todoLine.ToString()
|
|
||||||
})
|
|
||||||
|
|
||||||
return strings.Join(slices.Reverse(lines), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *RebaseCommands) BuildTodoLines(commits []*models.Commit, f func(*models.Commit, int) string) []TodoLine {
|
|
||||||
return slices.MapWithIndex(commits, func(commit *models.Commit, i int) TodoLine {
|
|
||||||
return TodoLine{Action: f(commit, i), Commit: commit}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *RebaseCommands) BuildTodoLinesSingleAction(commits []*models.Commit, action string) []TodoLine {
|
|
||||||
return self.BuildTodoLines(commits, func(commit *models.Commit, i int) string {
|
|
||||||
return action
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type TodoLine struct {
|
|
||||||
Action string
|
|
||||||
Commit *models.Commit
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *TodoLine) ToString() string {
|
|
||||||
if self.Action == "break" {
|
|
||||||
return self.Action + "\n"
|
|
||||||
} else {
|
|
||||||
return self.Action + " " + self.Commit.Sha + " " + self.Commit.Name + "\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// we can't start an interactive rebase from the first commit without passing the
|
// we can't start an interactive rebase from the first commit without passing the
|
||||||
// '--root' arg
|
// '--root' arg
|
||||||
func getBaseShaOrRoot(commits []*models.Commit, index int) string {
|
func getBaseShaOrRoot(commits []*models.Commit, index int) string {
|
||||||
|
@ -2,6 +2,7 @@ package git_commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
@ -63,7 +64,7 @@ func TestRebaseSkipEditorCommand(t *testing.T) {
|
|||||||
`^EDITOR=.*$`,
|
`^EDITOR=.*$`,
|
||||||
`^GIT_EDITOR=.*$`,
|
`^GIT_EDITOR=.*$`,
|
||||||
`^GIT_SEQUENCE_EDITOR=.*$`,
|
`^GIT_SEQUENCE_EDITOR=.*$`,
|
||||||
"^" + daemon.DaemonKindEnvKey + "=" + string(daemon.ExitImmediately) + "$",
|
"^" + daemon.DaemonKindEnvKey + "=" + strconv.Itoa(int(daemon.DaemonKindExitImmediately)) + "$",
|
||||||
} {
|
} {
|
||||||
regexStr := regexStr
|
regexStr := regexStr
|
||||||
foundMatch := lo.ContainsBy(envVars, func(envVar string) bool {
|
foundMatch := lo.ContainsBy(envVars, func(envVar string) bool {
|
||||||
|
Reference in New Issue
Block a user