diff --git a/pkg/app/daemon/daemon.go b/pkg/app/daemon/daemon.go index 0bd457099..7053a8032 100644 --- a/pkg/app/daemon/daemon.go +++ b/pkg/app/daemon/daemon.go @@ -1,8 +1,7 @@ package daemon import ( - "fmt" - "io" + "encoding/json" "log" "os" "path/filepath" @@ -32,39 +31,39 @@ const ( 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 a json-encoded instance of the InteractiveRebaseInstructions struct + InteractiveRebaseInstructionsEnvKey string = "LAZYGIT_DAEMON_INSTRUCTIONS" +) - // 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 +// 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 + + // 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. // 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" + ChangeTodoActions []ChangeTodoAction // Can be set to the sha of a "pick" todo that will be moved down by one. - MoveTodoDownEnvKey string = "LAZYGIT_MOVE_COMMIT_DOWN" + ShaToMoveDown string // Can be set to the sha of a "pick" todo that will be moved up by one. - MoveTodoUpEnvKey string = "LAZYGIT_MOVE_COMMIT_UP" -) + ShaToMoveUp string +} + +type ChangeTodoAction struct { + Sha string + NewAction todo.TodoCommand +} type Daemon interface { Run() error } -var logFile io.StringWriter - func Handle(common *common.Common) { - logFile, _ = os.Create("/tmp/daemon-log.txt") - _, _ = logFile.WriteString("Hello Daemon\n") - d := getDaemon(common) if d == nil { return @@ -118,43 +117,30 @@ func (self *rebaseDaemon) Run() error { } func (self *rebaseDaemon) writeTodoFile(path string) error { - if changeTodoActionEnvValue := os.Getenv(ChangeTodoActionEnvKey); changeTodoActionEnvValue != "" { - return self.changeTodoAction(path, changeTodoActionEnvValue) - } else if shaToMoveDown := os.Getenv(MoveTodoDownEnvKey); shaToMoveDown != "" { - _, _ = logFile.WriteString(fmt.Sprintf("Moving commit down: %s\n", shaToMoveDown)) - return utils.MoveTodoDown(path, shaToMoveDown, todo.Pick) - } else if shaToMoveUp := os.Getenv(MoveTodoUpEnvKey); shaToMoveUp != "" { - _, _ = logFile.WriteString(fmt.Sprintf("Moving commit up: %s\n", shaToMoveUp)) - return utils.MoveTodoUp(path, shaToMoveUp, todo.Pick) - } else { - todoContent := []byte(os.Getenv(RebaseTODOEnvKey)) - - prependLines := os.Getenv(PrependLinesEnvKey) != "" - if prependLines { - existingContent, err := os.ReadFile(path) - if err != nil { - return err - } - - todoContent = append(todoContent, existingContent...) - } - - return os.WriteFile(path, todoContent, 0o644) + jsonData := os.Getenv(InteractiveRebaseInstructionsEnvKey) + instructions := InteractiveRebaseInstructions{} + err := json.Unmarshal([]byte(jsonData), &instructions) + if err != nil { + return err } + + 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) + } + + self.c.Log.Error("No instructions were given to daemon") + return nil } -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 { +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 } } @@ -162,16 +148,6 @@ func (self *rebaseDaemon) changeTodoAction(path string, changeTodoActionEnvValue 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 { dir := env.GetGitDirEnv() if dir == "" { diff --git a/pkg/commands/git_commands/patch.go b/pkg/commands/git_commands/patch.go index a09f9bce6..0f8bfed9d 100644 --- a/pkg/commands/git_commands/patch.go +++ b/pkg/commands/git_commands/patch.go @@ -5,6 +5,7 @@ import ( "github.com/fsmiamoto/git-todo-parser/todo" "github.com/go-errors/errors" + "github.com/jesseduffield/lazygit/pkg/app/daemon" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/patch" "github.com/jesseduffield/lazygit/pkg/commands/types/enums" @@ -107,9 +108,11 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s err := self.rebase.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ baseShaOrRoot: commits[baseIndex].Sha, overrideEditor: true, - changeTodoActions: []ChangeTodoAction{ - {sha: commits[sourceCommitIdx].Sha, newAction: todo.Edit}, - {sha: commits[destinationCommitIdx].Sha, newAction: todo.Edit}, + instruction: ChangeTodoActionsInstruction{ + actions: []daemon.ChangeTodoAction{ + {Sha: commits[sourceCommitIdx].Sha, NewAction: todo.Edit}, + {Sha: commits[destinationCommitIdx].Sha, NewAction: todo.Edit}, + }, }, }).Run() if err != nil { diff --git a/pkg/commands/git_commands/rebase.go b/pkg/commands/git_commands/rebase.go index 13c978a92..ead8da27a 100644 --- a/pkg/commands/git_commands/rebase.go +++ b/pkg/commands/git_commands/rebase.go @@ -1,6 +1,7 @@ package git_commands import ( + "encoding/json" "fmt" "path/filepath" "strings" @@ -57,11 +58,11 @@ func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, me func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index int) (oscommands.ICmdObj, error) { return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ baseShaOrRoot: getBaseShaOrRoot(commits, index+1), - changeTodoActions: []ChangeTodoAction{ - { - sha: commits[index].Sha, - newAction: todo.Reword, - }, + instruction: ChangeTodoActionsInstruction{ + actions: []daemon.ChangeTodoAction{{ + Sha: commits[index].Sha, + NewAction: todo.Reword, + }}, }, }), nil } @@ -103,8 +104,8 @@ func (self *RebaseCommands) MoveCommitDown(commits []*models.Commit, index int) return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ baseShaOrRoot: baseShaOrRoot, + instruction: MoveDownInstruction{sha: commits[index].Sha}, overrideEditor: true, - moveDown: commits[index].Sha, }).Run() } @@ -113,8 +114,8 @@ func (self *RebaseCommands) MoveCommitUp(commits []*models.Commit, index int) er return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ baseShaOrRoot: baseShaOrRoot, + instruction: MoveUpInstruction{sha: commits[index].Sha}, overrideEditor: true, - moveUp: commits[index].Sha, }).Run() } @@ -127,12 +128,14 @@ func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index in baseShaOrRoot := getBaseShaOrRoot(commits, baseIndex) return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ - baseShaOrRoot: baseShaOrRoot, + baseShaOrRoot: baseShaOrRoot, + instruction: ChangeTodoActionsInstruction{ + actions: []daemon.ChangeTodoAction{{ + Sha: commits[index].Sha, + NewAction: action, + }}, + }, overrideEditor: true, - changeTodoActions: []ChangeTodoAction{{ - sha: commits[index].Sha, - newAction: action, - }}, }).Run() } @@ -140,59 +143,72 @@ func (self *RebaseCommands) EditRebase(branchRef string) error { commands := []TodoLine{{Action: "break"}} return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ baseShaOrRoot: branchRef, - todoLines: commands, - prepend: true, + instruction: PrependLinesInstruction{todoLines: commands}, }).Run() } -type ChangeTodoAction struct { - sha string - newAction todo.TodoCommand +type InteractiveRebaseInstruction interface { + // Add our data to the instructions struct, and return a log 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) + }), "\n") + 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 { - baseShaOrRoot string - todoLines []TodoLine - overrideEditor bool - prepend bool - changeTodoActions []ChangeTodoAction - moveDown string - moveUp string + baseShaOrRoot string + instruction InteractiveRebaseInstruction + overrideEditor bool } // PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase // we tell git to run lazygit to edit the todo list, and we pass the client // lazygit a todo string to write to the todo file func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteractiveRebaseCommandOpts) oscommands.ICmdObj { - todo := self.buildTodo(opts.todoLines) ex := oscommands.GetLazygitPath() - prependLines := "" - if opts.prepend { - prependLines = "TRUE" - } debug := "FALSE" if self.Debug { debug = "TRUE" } - changeTodoValue := strings.Join(slices.Map(opts.changeTodoActions, func(c ChangeTodoAction) string { - return fmt.Sprintf("%s:%s", c.sha, c.newAction) - }), "\n") - - moveDownValue := "" - if opts.moveDown != "" { - moveDownValue = opts.moveDown - } - moveUpValue := "" - if opts.moveUp != "" { - moveUpValue = opts.moveUp - } - - 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 = "" @@ -204,25 +220,24 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteract cmdObj := self.cmd.New(cmdStr) gitSequenceEditor := ex - 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 if moveDownValue != "" { - self.os.LogCommand(fmt.Sprintf("Moving TODO down: %s", moveDownValue), false) - } else if moveUpValue != "" { - self.os.LogCommand(fmt.Sprintf("Moving TODO up: %s", moveUpValue), false) + + if opts.instruction != nil { + instructions := daemon.InteractiveRebaseInstructions{} + 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 { gitSequenceEditor = "true" } cmdObj.AddEnvVars( daemon.DaemonKindEnvKey+"="+string(daemon.InteractiveRebase), - daemon.RebaseTODOEnvKey+"="+todo, - daemon.PrependLinesEnvKey+"="+prependLines, - daemon.ChangeTodoActionEnvKey+"="+changeTodoValue, - daemon.MoveTodoDownEnvKey+"="+moveDownValue, - daemon.MoveTodoUpEnvKey+"="+moveUpValue, "DEBUG="+debug, "LANG=en_US.UTF-8", // Force using EN as language "LC_ALL=en_US.UTF-8", // Force using EN as language @@ -297,10 +312,12 @@ func (self *RebaseCommands) BeginInteractiveRebaseForCommit(commits []*models.Co return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ baseShaOrRoot: getBaseShaOrRoot(commits, commitIndex+1), overrideEditor: true, - changeTodoActions: []ChangeTodoAction{{ - sha: commits[commitIndex].Sha, - newAction: todo.Edit, - }}, + instruction: ChangeTodoActionsInstruction{ + actions: []daemon.ChangeTodoAction{{ + Sha: commits[commitIndex].Sha, + NewAction: todo.Edit, + }}, + }, }).Run() } @@ -393,11 +410,13 @@ func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error { return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ baseShaOrRoot: "HEAD", - todoLines: todoLines, + instruction: PrependLinesInstruction{ + todoLines: todoLines, + }, }).Run() } -func (self *RebaseCommands) buildTodo(todoLines []TodoLine) string { +func TodoLinesToString(todoLines []TodoLine) string { lines := slices.Map(todoLines, func(todoLine TodoLine) string { return todoLine.ToString() }) diff --git a/pkg/utils/rebase_todo.go b/pkg/utils/rebase_todo.go index a61d4c36c..f34e11774 100644 --- a/pkg/utils/rebase_todo.go +++ b/pkg/utils/rebase_todo.go @@ -63,6 +63,16 @@ func WriteRebaseTodoFile(fileName string, todos []todo.Todo) error { return err } +func PrependStrToTodoFile(filePath string, linesToPrepend []byte) error { + existingContent, err := os.ReadFile(filePath) + if err != nil { + return err + } + + linesToPrepend = append(linesToPrepend, existingContent...) + return os.WriteFile(filePath, linesToPrepend, 0o644) +} + func MoveTodoDown(fileName string, sha string, action todo.TodoCommand) error { todos, err := ReadRebaseTodoFile(fileName) if err != nil {