mirror of
https://github.com/jesseduffield/lazygit.git
synced 2024-12-02 09:21:40 +02:00
9ae7710850
Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
185 lines
5.2 KiB
Go
185 lines
5.2 KiB
Go
package utils
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/fsmiamoto/git-todo-parser/todo"
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
// Read a git-rebase-todo file, change the action for the given sha to
|
|
// newAction, and write it back
|
|
func EditRebaseTodo(filePath string, sha string, oldAction todo.TodoCommand, newAction todo.TodoCommand, commentChar byte) error {
|
|
todos, err := ReadRebaseTodoFile(filePath, commentChar)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for i := range todos {
|
|
t := &todos[i]
|
|
// Comparing just the sha is not enough; we need to compare both the
|
|
// action and the sha, as the sha could appear multiple times (e.g. in a
|
|
// pick and later in a merge)
|
|
if t.Command == oldAction && equalShas(t.Commit, sha) {
|
|
t.Command = newAction
|
|
return WriteRebaseTodoFile(filePath, todos, commentChar)
|
|
}
|
|
}
|
|
|
|
// Should never get here
|
|
return fmt.Errorf("Todo %s not found in git-rebase-todo", sha)
|
|
}
|
|
|
|
func equalShas(a, b string) bool {
|
|
return strings.HasPrefix(a, b) || strings.HasPrefix(b, a)
|
|
}
|
|
|
|
func ReadRebaseTodoFile(fileName string, commentChar byte) ([]todo.Todo, error) {
|
|
f, err := os.Open(fileName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
todos, err := todo.Parse(f, commentChar)
|
|
err2 := f.Close()
|
|
if err == nil {
|
|
err = err2
|
|
}
|
|
return todos, err
|
|
}
|
|
|
|
func WriteRebaseTodoFile(fileName string, todos []todo.Todo, commentChar byte) error {
|
|
f, err := os.Create(fileName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = todo.Write(f, todos, commentChar)
|
|
err2 := f.Close()
|
|
if err == nil {
|
|
err = err2
|
|
}
|
|
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, commentChar byte) error {
|
|
todos, err := ReadRebaseTodoFile(fileName, commentChar)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rearrangedTodos, err := moveTodoDown(todos, sha, action)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return WriteRebaseTodoFile(fileName, rearrangedTodos, commentChar)
|
|
}
|
|
|
|
func MoveTodoUp(fileName string, sha string, action todo.TodoCommand, commentChar byte) error {
|
|
todos, err := ReadRebaseTodoFile(fileName, commentChar)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rearrangedTodos, err := moveTodoUp(todos, sha, action)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return WriteRebaseTodoFile(fileName, rearrangedTodos, commentChar)
|
|
}
|
|
|
|
func moveTodoDown(todos []todo.Todo, sha string, action todo.TodoCommand) ([]todo.Todo, error) {
|
|
rearrangedTodos, err := moveTodoUp(lo.Reverse(todos), sha, action)
|
|
return lo.Reverse(rearrangedTodos), err
|
|
}
|
|
|
|
func moveTodoUp(todos []todo.Todo, sha string, action todo.TodoCommand) ([]todo.Todo, error) {
|
|
_, sourceIdx, ok := lo.FindIndexOf(todos, func(t todo.Todo) bool {
|
|
// Comparing just the sha is not enough; we need to compare both the
|
|
// action and the sha, as the sha could appear multiple times (e.g. in a
|
|
// pick and later in a merge)
|
|
return t.Command == action && equalShas(t.Commit, sha)
|
|
})
|
|
|
|
if !ok {
|
|
// Should never happen
|
|
return []todo.Todo{}, fmt.Errorf("Todo %s not found in git-rebase-todo", sha)
|
|
}
|
|
|
|
// The todos are ordered backwards compared to our model commits, so
|
|
// actually move the commit _down_ in the todos slice (i.e. towards
|
|
// the end of the slice)
|
|
|
|
// Find the next todo that we show in lazygit's commits view (skipping the rest)
|
|
_, skip, ok := lo.FindIndexOf(todos[sourceIdx+1:], isRenderedTodo)
|
|
|
|
if !ok {
|
|
// We expect callers to guard against this
|
|
return []todo.Todo{}, fmt.Errorf("Destination position for moving todo is out of range")
|
|
}
|
|
|
|
destinationIdx := sourceIdx + 1 + skip
|
|
|
|
rearrangedTodos := MoveElement(todos, sourceIdx, destinationIdx)
|
|
|
|
return rearrangedTodos, nil
|
|
}
|
|
|
|
func MoveFixupCommitDown(fileName string, originalSha string, fixupSha string, commentChar byte) error {
|
|
todos, err := ReadRebaseTodoFile(fileName, commentChar)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newTodos, err := moveFixupCommitDown(todos, originalSha, fixupSha)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return WriteRebaseTodoFile(fileName, newTodos, commentChar)
|
|
}
|
|
|
|
func moveFixupCommitDown(todos []todo.Todo, originalSha string, fixupSha string) ([]todo.Todo, error) {
|
|
isOriginal := func(t todo.Todo) bool {
|
|
return t.Command == todo.Pick && equalShas(t.Commit, originalSha)
|
|
}
|
|
|
|
isFixup := func(t todo.Todo) bool {
|
|
return t.Command == todo.Pick && equalShas(t.Commit, fixupSha)
|
|
}
|
|
|
|
originalShaCount := lo.CountBy(todos, isOriginal)
|
|
if originalShaCount != 1 {
|
|
return nil, fmt.Errorf("Expected exactly one original SHA, found %d", originalShaCount)
|
|
}
|
|
|
|
fixupShaCount := lo.CountBy(todos, isFixup)
|
|
if fixupShaCount != 1 {
|
|
return nil, fmt.Errorf("Expected exactly one fixup SHA, found %d", fixupShaCount)
|
|
}
|
|
|
|
_, fixupIndex, _ := lo.FindIndexOf(todos, isFixup)
|
|
_, originalIndex, _ := lo.FindIndexOf(todos, isOriginal)
|
|
|
|
newTodos := MoveElement(todos, fixupIndex, originalIndex+1)
|
|
|
|
newTodos[originalIndex+1].Command = todo.Fixup
|
|
|
|
return newTodos, nil
|
|
}
|
|
|
|
// We render a todo in the commits view if it's a commit or if it's an
|
|
// update-ref. We don't render label, reset, or comment lines.
|
|
func isRenderedTodo(t todo.Todo) bool {
|
|
return t.Commit != "" || t.Command == todo.UpdateRef
|
|
}
|