summaryrefslogtreecommitdiffstats
path: root/pkg/app/daemon/daemon.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/app/daemon/daemon.go')
-rw-r--r--pkg/app/daemon/daemon.go300
1 files changed, 240 insertions, 60 deletions
diff --git a/pkg/app/daemon/daemon.go b/pkg/app/daemon/daemon.go
index e040e79c2..adc287309 100644
--- a/pkg/app/daemon/daemon.go
+++ b/pkg/app/daemon/daemon.go
@@ -1,13 +1,17 @@
package daemon
import (
+ "encoding/json"
+ "fmt"
"log"
"os"
- "path/filepath"
- "strings"
+ "strconv"
+ "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/env"
+ "github.com/jesseduffield/lazygit/pkg/utils"
+ "github.com/samber/lo"
)
// Sometimes lazygit will be invoked in daemon mode from a parent lazygit process.
@@ -15,38 +19,58 @@ import (
// 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
// 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
-// as a daemon which simply exits immediately. Any additional arguments we want
-// to pass to a daemon can be done via other env vars.
+// 'LAZYGIT_DAEMON_KIND=1' (exit immediately) to specify that we want to run lazygit
+// as a daemon which simply exits immediately.
+//
+// '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 (
- InteractiveRebase DaemonKind = "INTERACTIVE_REBASE"
- ExitImmediately DaemonKind = "EXIT_IMMEDIATELY"
+ // for when we fail to parse the daemon kind
+ DaemonKindUnknown DaemonKind = iota
+
+ DaemonKindExitImmediately
+ DaemonKindCherryPick
+ DaemonKindMoveTodoUp
+ DaemonKindMoveTodoDown
+ DaemonKindInsertBreak
+ DaemonKindChangeTodoActions
+ DaemonKindMoveFixupCommitDown
)
const (
DaemonKindEnvKey string = "LAZYGIT_DAEMON_KIND"
- RebaseTODOEnvKey string = "LAZYGIT_REBASE_TODO"
- // The `PrependLinesEnvKey` env variable is set to `true` to tell our daemon
- // 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"
+ // Contains json-encoded arguments to the daemon
+ DaemonInstructionEnvKey string = "LAZYGIT_DAEMON_INSTRUCTION"
)
-type Daemon interface {
- Run() error
+func getInstruction() Instruction {
+ jsonData := os.Getenv(DaemonInstructionEnvKey)
+
+ mapping := map[DaemonKind]func(string) Instruction{
+ DaemonKindExitImmediately: deserializeInstruction[*ExitImmediatelyInstruction],
+ DaemonKindCherryPick: deserializeInstruction[*CherryPickCommitsInstruction],
+ DaemonKindChangeTodoActions: deserializeInstruction[*ChangeTodoActionsInstruction],
+ DaemonKindMoveFixupCommitDown: deserializeInstruction[*MoveFixupCommitDownInstruction],
+ DaemonKindMoveTodoUp: deserializeInstruction[*MoveTodoUpInstruction],
+ DaemonKindMoveTodoDown: deserializeInstruction[*MoveTodoDownInstruction],
+ DaemonKindInsertBreak: deserializeInstruction[*InsertBreakInstruction],
+ }
+
+ return mapping[getDaemonKind()](jsonData)
}
func Handle(common *common.Common) {
- d := getDaemon(common)
- if d == nil {
+ if !InDaemonMode() {
return
}
- if err := d.Run(); err != nil {
+ instruction := getInstruction()
+
+ if err := instruction.run(common); err != nil {
log.Fatal(err)
}
@@ -54,73 +78,229 @@ func Handle(common *common.Common) {
}
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}
+func getDaemonKind() DaemonKind {
+ intValue, err := strconv.Atoi(os.Getenv(DaemonKindEnvKey))
+ if err != nil {
+ return DaemonKindUnknown
}
- return nil
+ return DaemonKind(intValue)
}
-func getDaemonKind() DaemonKind {
- return DaemonKind(os.Getenv(DaemonKindEnvKey))
+// An Instruction is a command to be run by lazygit in daemon mode.
+// It is serialized to json and passed to lazygit via environment variables
+type Instruction interface {
+ Kind() DaemonKind
+ SerializedInstructions() string
+
+ // runs the instruction
+ run(common *common.Common) error
}
-type rebaseDaemon struct {
- c *common.Common
+func serializeInstruction[T any](instruction T) string {
+ jsonData, err := json.Marshal(instruction)
+ if err != nil {
+ // this should never happen
+ panic(err)
+ }
+
+ return string(jsonData)
}
-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]
+func deserializeInstruction[T Instruction](jsonData string) Instruction {
+ var instruction T
+ err := json.Unmarshal([]byte(jsonData), &instruction)
+ if err != nil {
+ panic(err)
+ }
- 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 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
}
-func (self *rebaseDaemon) writeTodoFile(path string) error {
- todoContent := []byte(os.Getenv(RebaseTODOEnvKey))
+func NewExitImmediatelyInstruction() Instruction {
+ return &ExitImmediatelyInstruction{}
+}
+
+type CherryPickCommitsInstruction struct {
+ Todo string
+}
- prependLines := os.Getenv(PrependLinesEnvKey) != ""
- if prependLines {
- existingContent, err := os.ReadFile(path)
- if err != nil {
- return err
+func NewCherryPickCommitsInstruction(commits []*models.Commit) Instruction {
+ todoLines := lo.Map(commits, func(commit *models.Commit, _ int) TodoLine {
+ return TodoLine{
+ Action: "pick",
+ Commit: commit,
}
+ })
- todoContent = append(todoContent, existingContent...)
+ todo := TodoLinesToString(todoLines)
+
+ return &CherryPickCommitsInstruction{
+ Todo: todo,
}
+}
- return os.WriteFile(path, todoContent, 0o644)
+func (self *CherryPickCommitsInstruction) Kind() DaemonKind {
+ return DaemonKindCherryPick
}
-func gitDir() string {
- dir := env.GetGitDirEnv()
- 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 {
- c *common.Common
+func (self *ChangeTodoActionsInstruction) Kind() DaemonKind {
+ return DaemonKindChangeTodoActions
}
-func (self *exitImmediatelyDaemon) Run() error {
- return nil
+func (self *ChangeTodoActionsInstruction) SerializedInstructions() string {
+ 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
+ })
+}
+
+// Takes the sha of some commit, and the sha of a fixup commit that was created
+// at the end of the branch, then moves the fixup commit down to right after the
+// original commit, changing its type to "fixup"
+type MoveFixupCommitDownInstruction struct {
+ OriginalSha string
+ FixupSha string
+}
+
+func NewMoveFixupCommitDownInstruction(originalSha string, fixupSha string) Instruction {
+ return &MoveFixupCommitDownInstruction{
+ OriginalSha: originalSha,
+ FixupSha: fixupSha,
+ }
+}
+
+func (self *MoveFixupCommitDownInstruction) Kind() DaemonKind {
+ return DaemonKindMoveFixupCommitDown
+}
+
+func (self *MoveFixupCommitDownInstruction) SerializedInstructions() string {
+ return serializeInstruction(self)
+}
+
+func (self *MoveFixupCommitDownInstruction) run(common *common.Common) error {
+ return handleInteractiveRebase(common, func(path string) error {
+ return utils.MoveFixupCommitDown(path, self.OriginalSha, self.FixupSha)
+ })
+}
+
+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"))
+ })
}