2022-05-07 07:23:08 +02:00
|
|
|
package daemon
|
|
|
|
|
|
|
|
import (
|
2023-04-06 11:13:42 +02:00
|
|
|
"encoding/json"
|
2022-05-07 07:23:08 +02:00
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
2023-04-05 19:01:49 +02:00
|
|
|
"github.com/fsmiamoto/git-todo-parser/todo"
|
2022-05-07 07:23:08 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/common"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/env"
|
2023-04-05 19:01:49 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
2022-05-07 07:23:08 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// Sometimes lazygit will be invoked in daemon mode from a parent lazygit process.
|
|
|
|
// We do this when git lets us supply a program to run within a git command.
|
|
|
|
// 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.
|
|
|
|
|
|
|
|
type DaemonKind string
|
|
|
|
|
|
|
|
const (
|
|
|
|
InteractiveRebase DaemonKind = "INTERACTIVE_REBASE"
|
|
|
|
ExitImmediately DaemonKind = "EXIT_IMMEDIATELY"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
DaemonKindEnvKey string = "LAZYGIT_DAEMON_KIND"
|
2023-03-29 00:49:19 +02:00
|
|
|
|
2023-04-06 11:13:42 +02:00
|
|
|
// Contains a json-encoded instance of the InteractiveRebaseInstructions struct
|
|
|
|
InteractiveRebaseInstructionsEnvKey string = "LAZYGIT_DAEMON_INSTRUCTIONS"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Exactly one of the fields in this struct is expected to be non-empty
|
|
|
|
type InteractiveRebaseInstructions struct {
|
|
|
|
// 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
|
2023-04-05 19:01:49 +02:00
|
|
|
|
2023-04-06 11:13:42 +02:00
|
|
|
// If this is non-empty, it tells lazygit to read the original todo file, and
|
|
|
|
// change the action for one or more entries in it.
|
2023-04-05 19:01:49 +02:00
|
|
|
// The existing action of the todo to be changed is expected to be "pick".
|
2023-04-06 11:13:42 +02:00
|
|
|
ChangeTodoActions []ChangeTodoAction
|
2023-04-06 09:53:10 +02:00
|
|
|
|
|
|
|
// Can be set to the sha of a "pick" todo that will be moved down by one.
|
2023-04-06 11:13:42 +02:00
|
|
|
ShaToMoveDown string
|
2023-04-06 09:53:10 +02:00
|
|
|
|
|
|
|
// Can be set to the sha of a "pick" todo that will be moved up by one.
|
2023-04-06 11:13:42 +02:00
|
|
|
ShaToMoveUp string
|
|
|
|
}
|
|
|
|
|
|
|
|
type ChangeTodoAction struct {
|
|
|
|
Sha string
|
|
|
|
NewAction todo.TodoCommand
|
|
|
|
}
|
2022-05-07 07:23:08 +02:00
|
|
|
|
|
|
|
type Daemon interface {
|
|
|
|
Run() error
|
|
|
|
}
|
|
|
|
|
|
|
|
func Handle(common *common.Common) {
|
|
|
|
d := getDaemon(common)
|
|
|
|
if d == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := d.Run(); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
func InDaemonMode() bool {
|
|
|
|
return getDaemonKind() != ""
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
return DaemonKind(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)
|
2023-04-02 03:37:02 +02:00
|
|
|
path := os.Args[1]
|
2023-03-29 00:49:19 +02:00
|
|
|
|
2023-04-02 03:37:02 +02:00
|
|
|
if strings.HasSuffix(path, "git-rebase-todo") {
|
|
|
|
return self.writeTodoFile(path)
|
|
|
|
} else if strings.HasSuffix(path, filepath.Join(gitDir(), "COMMIT_EDITMSG")) { // TODO: test
|
2022-05-07 07:23:08 +02:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2023-04-02 03:37:02 +02:00
|
|
|
func (self *rebaseDaemon) writeTodoFile(path string) error {
|
2023-04-06 11:13:42 +02:00
|
|
|
jsonData := os.Getenv(InteractiveRebaseInstructionsEnvKey)
|
|
|
|
instructions := InteractiveRebaseInstructions{}
|
|
|
|
err := json.Unmarshal([]byte(jsonData), &instructions)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2023-04-05 19:01:49 +02:00
|
|
|
}
|
2023-04-02 03:37:02 +02:00
|
|
|
|
2023-04-06 11:13:42 +02:00
|
|
|
if instructions.LinesToPrependToRebaseTODO != "" {
|
|
|
|
return utils.PrependStrToTodoFile(path, []byte(instructions.LinesToPrependToRebaseTODO))
|
|
|
|
} else if len(instructions.ChangeTodoActions) != 0 {
|
|
|
|
return self.changeTodoAction(path, instructions.ChangeTodoActions)
|
|
|
|
} else if instructions.ShaToMoveDown != "" {
|
|
|
|
return utils.MoveTodoDown(path, instructions.ShaToMoveDown, todo.Pick)
|
|
|
|
} else if instructions.ShaToMoveUp != "" {
|
|
|
|
return utils.MoveTodoUp(path, instructions.ShaToMoveUp, todo.Pick)
|
2023-04-05 19:01:49 +02:00
|
|
|
}
|
2023-04-02 03:37:02 +02:00
|
|
|
|
2023-04-06 11:13:42 +02:00
|
|
|
self.c.Log.Error("No instructions were given to daemon")
|
2023-04-05 19:01:49 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-04-06 11:13:42 +02:00
|
|
|
func (self *rebaseDaemon) changeTodoAction(path string, changeTodoActions []ChangeTodoAction) error {
|
|
|
|
for _, c := range changeTodoActions {
|
|
|
|
if err := utils.EditRebaseTodo(path, c.Sha, todo.Pick, c.NewAction); err != nil {
|
|
|
|
return err
|
2023-04-05 19:01:49 +02:00
|
|
|
}
|
2023-04-02 03:37:02 +02:00
|
|
|
}
|
|
|
|
|
2023-04-06 11:13:42 +02:00
|
|
|
return nil
|
2023-04-02 03:37:02 +02:00
|
|
|
}
|
|
|
|
|
2022-05-07 07:23:08 +02:00
|
|
|
func gitDir() string {
|
|
|
|
dir := env.GetGitDirEnv()
|
|
|
|
if dir == "" {
|
|
|
|
return ".git"
|
|
|
|
}
|
|
|
|
return dir
|
|
|
|
}
|
|
|
|
|
|
|
|
type exitImmediatelyDaemon struct {
|
|
|
|
c *common.Common
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *exitImmediatelyDaemon) Run() error {
|
|
|
|
return nil
|
|
|
|
}
|