1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-04-25 12:24:47 +02:00

Fix deleting update ref todos (#3439)

- **PR Description**

Deleting an update-ref todo in an interactive rebase now behaves as
expected (i.e. it leaves the branch referenced by the update-ref
untouched). Previously it would have deleted the branch referenced by
the update-ref todo when the rebase was continued. See #3418 for more
details.

Fixes #3418.
This commit is contained in:
Stefan Haller 2024-03-26 22:43:46 +01:00 committed by GitHub
commit e295ccefab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 88 additions and 8 deletions

View File

@ -39,6 +39,7 @@ const (
DaemonKindInsertBreak DaemonKindInsertBreak
DaemonKindChangeTodoActions DaemonKindChangeTodoActions
DaemonKindMoveFixupCommitDown DaemonKindMoveFixupCommitDown
DaemonKindWriteRebaseTodo
) )
const ( const (
@ -59,6 +60,7 @@ func getInstruction() Instruction {
DaemonKindMoveTodosUp: deserializeInstruction[*MoveTodosUpInstruction], DaemonKindMoveTodosUp: deserializeInstruction[*MoveTodosUpInstruction],
DaemonKindMoveTodosDown: deserializeInstruction[*MoveTodosDownInstruction], DaemonKindMoveTodosDown: deserializeInstruction[*MoveTodosDownInstruction],
DaemonKindInsertBreak: deserializeInstruction[*InsertBreakInstruction], DaemonKindInsertBreak: deserializeInstruction[*InsertBreakInstruction],
DaemonKindWriteRebaseTodo: deserializeInstruction[*WriteRebaseTodoInstruction],
} }
return mapping[getDaemonKind()](jsonData) return mapping[getDaemonKind()](jsonData)
@ -330,3 +332,27 @@ func (self *InsertBreakInstruction) run(common *common.Common) error {
return utils.PrependStrToTodoFile(path, []byte("break\n")) return utils.PrependStrToTodoFile(path, []byte("break\n"))
}) })
} }
type WriteRebaseTodoInstruction struct {
TodosFileContent []byte
}
func NewWriteRebaseTodoInstruction(todosFileContent []byte) Instruction {
return &WriteRebaseTodoInstruction{
TodosFileContent: todosFileContent,
}
}
func (self *WriteRebaseTodoInstruction) Kind() DaemonKind {
return DaemonKindWriteRebaseTodo
}
func (self *WriteRebaseTodoInstruction) SerializedInstructions() string {
return serializeInstruction(self)
}
func (self *WriteRebaseTodoInstruction) run(common *common.Common) error {
return handleInteractiveRebase(common, func(path string) error {
return os.WriteFile(path, self.TodosFileContent, 0o644)
})
}

View File

@ -204,7 +204,7 @@ type PrepareInteractiveRebaseCommandOpts struct {
// PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase // PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase
// we tell git to run lazygit to edit the todo list, and we pass the client // 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 // lazygit instructions what to do with the todo file
func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteractiveRebaseCommandOpts) oscommands.ICmdObj { func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteractiveRebaseCommandOpts) oscommands.ICmdObj {
ex := oscommands.GetLazygitPath() ex := oscommands.GetLazygitPath()
@ -250,6 +250,36 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteract
return cmdObj return cmdObj
} }
// GitRebaseEditTodo runs "git rebase --edit-todo", saving the given todosFileContent to the file
func (self *RebaseCommands) GitRebaseEditTodo(todosFileContent []byte) error {
ex := oscommands.GetLazygitPath()
cmdArgs := NewGitCmd("rebase").
Arg("--edit-todo").
ToArgv()
debug := "FALSE"
if self.Debug {
debug = "TRUE"
}
self.Log.WithField("command", cmdArgs).Debug("RunCommand")
cmdObj := self.cmd.New(cmdArgs)
cmdObj.AddEnvVars(daemon.ToEnvVars(daemon.NewWriteRebaseTodoInstruction(todosFileContent))...)
cmdObj.AddEnvVars(
"DEBUG="+debug,
"LANG=en_US.UTF-8", // Force using EN as language
"LC_ALL=en_US.UTF-8", // Force using EN as language
"GIT_EDITOR="+ex,
"GIT_SEQUENCE_EDITOR="+ex,
)
return cmdObj.Run()
}
// AmendTo amends the given commit with whatever files are staged // AmendTo amends the given commit with whatever files are staged
func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) error { func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) error {
commit := commits[commitIndex] commit := commits[commitIndex]
@ -302,11 +332,16 @@ func (self *RebaseCommands) DeleteUpdateRefTodos(commits []*models.Commit) error
return todoFromCommit(commit) return todoFromCommit(commit)
}) })
return utils.DeleteTodos( todosFileContent, err := utils.DeleteTodos(
filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo"), filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo"),
todosToDelete, todosToDelete,
self.config.GetCoreCommentChar(), self.config.GetCoreCommentChar(),
) )
if err != nil {
return err
}
return self.GitRebaseEditTodo(todosFileContent)
} }
func (self *RebaseCommands) MoveTodosDown(commits []*models.Commit) error { func (self *RebaseCommands) MoveTodosDown(commits []*models.Commit) error {

View File

@ -50,6 +50,8 @@ var DeleteUpdateRefTodo = NewIntegrationTest(NewIntegrationTestArgs{
Contains("pick").Contains("CI commit 02"), Contains("pick").Contains("CI commit 02"),
Contains("CI ◯ <-- YOU ARE HERE --- commit 01"), Contains("CI ◯ <-- YOU ARE HERE --- commit 01"),
). ).
NavigateToLine(Contains("commit 02")).
Press(keys.Universal.Remove).
Tap(func() { Tap(func() {
t.Common().ContinueRebase() t.Common().ContinueRebase()
}). }).
@ -57,9 +59,14 @@ var DeleteUpdateRefTodo = NewIntegrationTest(NewIntegrationTestArgs{
Contains("CI ◯ commit 06"), Contains("CI ◯ commit 06"),
Contains("CI ◯ commit 05"), Contains("CI ◯ commit 05"),
Contains("CI ◯ commit 04"), Contains("CI ◯ commit 04"),
Contains("CI ◯ commit 03"), // No start on this commit, so there's no branch head here Contains("CI ◯ commit 03"), // No star on this commit, so there's no branch head here
Contains("CI ◯ commit 02"),
Contains("CI ◯ commit 01"), Contains("CI ◯ commit 01"),
) )
t.Views().Branches().
Lines(
Contains("branch2"),
Contains("branch1"),
)
}, },
}) })

View File

@ -1,6 +1,7 @@
package utils package utils
import ( import (
"bytes"
"fmt" "fmt"
"os" "os"
"strings" "strings"
@ -96,6 +97,12 @@ func WriteRebaseTodoFile(fileName string, todos []todo.Todo, commentChar byte) e
return err return err
} }
func todosToString(todos []todo.Todo, commentChar byte) ([]byte, error) {
buffer := bytes.Buffer{}
err := todo.Write(&buffer, todos, commentChar)
return buffer.Bytes(), err
}
func PrependStrToTodoFile(filePath string, linesToPrepend []byte) error { func PrependStrToTodoFile(filePath string, linesToPrepend []byte) error {
existingContent, err := os.ReadFile(filePath) existingContent, err := os.ReadFile(filePath)
if err != nil { if err != nil {
@ -106,16 +113,21 @@ func PrependStrToTodoFile(filePath string, linesToPrepend []byte) error {
return os.WriteFile(filePath, linesToPrepend, 0o644) return os.WriteFile(filePath, linesToPrepend, 0o644)
} }
func DeleteTodos(fileName string, todosToDelete []Todo, commentChar byte) error { // Unlike the other functions in this file, which write the changed todos file
// back to disk, this one returns the new content as a byte slice. This is
// because when deleting update-ref todos, we must perform a "git rebase
// --edit-todo" command to pass the changed todos to git so that it can do some
// housekeeping around the deleted todos. This can only be done by our caller.
func DeleteTodos(fileName string, todosToDelete []Todo, commentChar byte) ([]byte, error) {
todos, err := ReadRebaseTodoFile(fileName, commentChar) todos, err := ReadRebaseTodoFile(fileName, commentChar)
if err != nil { if err != nil {
return err return nil, err
} }
rearrangedTodos, err := deleteTodos(todos, todosToDelete) rearrangedTodos, err := deleteTodos(todos, todosToDelete)
if err != nil { if err != nil {
return err return nil, err
} }
return WriteRebaseTodoFile(fileName, rearrangedTodos, commentChar) return todosToString(rearrangedTodos, commentChar)
} }
func deleteTodos(todos []todo.Todo, todosToDelete []Todo) ([]todo.Todo, error) { func deleteTodos(todos []todo.Todo, todosToDelete []Todo) ([]todo.Todo, error) {