From 09068b03604de46187606b26604ba0717305a053 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Fri, 30 Oct 2026 15:13:18 +0100 Subject: [PATCH] Add a function to change which commit a rebase todo picks The next commit uses this to replay a modified copy of a commit in place of the original one. Co-Authored-By: Claude Opus 5.5 --- pkg/utils/rebase_todo.go | 27 ++++++++++++++ pkg/utils/rebase_todo_test.go | 69 +++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/pkg/utils/rebase_todo.go b/pkg/utils/rebase_todo.go index 3a3577f80..577b3da07 100644 --- a/pkg/utils/rebase_todo.go +++ b/pkg/utils/rebase_todo.go @@ -331,3 +331,30 @@ func dropMergeCommit(todos []todo.Todo, hash string) ([]todo.Todo, error) { _, idx, _ := lo.FindIndexOf(todos, isMerge) return slices.Delete(todos, idx, idx+1), nil } + +// Read a git-rebase-todo file, make the todo for the given commit use a +// different commit instead, and write it back +func ChangeTodoCommit(fileName string, hash string, newHash string, commentChar byte) error { + todos, err := ReadRebaseTodoFile(fileName, commentChar) + if err != nil { + return err + } + + newTodos, err := changeTodoCommit(todos, hash, newHash) + if err != nil { + return err + } + + return WriteRebaseTodoFile(fileName, newTodos, commentChar) +} + +func changeTodoCommit(todos []todo.Todo, hash string, newHash string) ([]todo.Todo, error) { + idx, ok := findTodo(todos, Todo{Hash: hash}) + if !ok { + // Should never happen + return nil, fmt.Errorf("Todo %s not found in git-rebase-todo", hash) + } + + todos[idx].Commit = newHash + return todos, nil +} diff --git a/pkg/utils/rebase_todo_test.go b/pkg/utils/rebase_todo_test.go index a9ac1bba5..b032e4057 100644 --- a/pkg/utils/rebase_todo_test.go +++ b/pkg/utils/rebase_todo_test.go @@ -596,6 +596,75 @@ func TestRebaseCommands_deleteTodos(t *testing.T) { } } +func TestRebaseCommands_changeTodoCommit(t *testing.T) { + scenarios := []struct { + name string + todos []todo.Todo + hash string + newHash string + expectedTodos []todo.Todo + expectedErr error + }{ + { + name: "success", + todos: []todo.Todo{ + {Command: todo.Pick, Commit: "1234", Msg: "first"}, + {Command: todo.Pick, Commit: "5678", Msg: "second"}, + {Command: todo.UpdateRef, Ref: "refs/heads/some_branch"}, + {Command: todo.Pick, Commit: "abcd", Msg: "third"}, + }, + hash: "5678", + newHash: "ef01", + expectedTodos: []todo.Todo{ + {Command: todo.Pick, Commit: "1234", Msg: "first"}, + {Command: todo.Pick, Commit: "ef01", Msg: "second"}, + {Command: todo.UpdateRef, Ref: "refs/heads/some_branch"}, + {Command: todo.Pick, Commit: "abcd", Msg: "third"}, + }, + expectedErr: nil, + }, + { + name: "abbreviated hash in todo", + todos: []todo.Todo{ + {Command: todo.Pick, Commit: "1234"}, + {Command: todo.Pick, Commit: "5678"}, + }, + hash: "56789abc", + newHash: "ef01", + expectedTodos: []todo.Todo{ + {Command: todo.Pick, Commit: "1234"}, + {Command: todo.Pick, Commit: "ef01"}, + }, + expectedErr: nil, + }, + { + name: "failure", + todos: []todo.Todo{ + {Command: todo.Pick, Commit: "1234"}, + {Command: todo.Pick, Commit: "5678"}, + }, + hash: "abcd", + newHash: "ef01", + expectedTodos: nil, + expectedErr: errors.New("Todo abcd not found in git-rebase-todo"), + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + actualTodos, actualErr := changeTodoCommit(scenario.todos, scenario.hash, scenario.newHash) + + if scenario.expectedErr == nil { + assert.NoError(t, actualErr) + } else { + assert.EqualError(t, actualErr, scenario.expectedErr.Error()) + } + + assert.EqualValues(t, scenario.expectedTodos, actualTodos) + }) + } +} + func Test_equalHash(t *testing.T) { scenarios := []struct { a string