summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Haller <stefan@haller-berlin.de>2024-03-13 07:58:39 +0100
committerStefan Haller <stefan@haller-berlin.de>2024-03-16 22:01:13 +0100
commit0608fc6471eacd2c4ffe33315972019839fc048b (patch)
tree583fde9bef12b93662033839ef2e9ed426c2cfc1
parent64a1a455d6648fc9b36a841dde1d88250b145b9b (diff)
Allow deleting update-ref todos
-rw-r--r--pkg/commands/git_commands/rebase.go12
-rw-r--r--pkg/gui/controllers/local_commits_controller.go54
-rw-r--r--pkg/i18n/english.go2
-rw-r--r--pkg/integration/tests/interactive_rebase/delete_update_ref_todo.go65
-rw-r--r--pkg/integration/tests/test_list.go1
-rw-r--r--pkg/utils/rebase_todo.go27
-rw-r--r--pkg/utils/rebase_todo_test.go55
7 files changed, 215 insertions, 1 deletions
diff --git a/pkg/commands/git_commands/rebase.go b/pkg/commands/git_commands/rebase.go
index c628ad414..eeae66167 100644
--- a/pkg/commands/git_commands/rebase.go
+++ b/pkg/commands/git_commands/rebase.go
@@ -297,6 +297,18 @@ func (self *RebaseCommands) EditRebaseTodo(commits []*models.Commit, action todo
)
}
+func (self *RebaseCommands) DeleteUpdateRefTodos(commits []*models.Commit) error {
+ todosToDelete := lo.Map(commits, func(commit *models.Commit, _ int) utils.Todo {
+ return todoFromCommit(commit)
+ })
+
+ return utils.DeleteTodos(
+ filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo"),
+ todosToDelete,
+ self.config.GetCoreCommentChar(),
+ )
+}
+
func (self *RebaseCommands) MoveTodosDown(commits []*models.Commit) error {
fileName := filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo")
todosToMove := lo.Map(commits, func(commit *models.Commit, _ int) utils.Todo {
diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go
index 4dd19109b..fe15a37d3 100644
--- a/pkg/gui/controllers/local_commits_controller.go
+++ b/pkg/gui/controllers/local_commits_controller.go
@@ -106,7 +106,7 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
Handler: self.withItemsRange(self.drop),
GetDisabledReason: self.require(
self.itemRangeSelected(
- self.midRebaseCommandEnabled,
+ self.canDropCommits,
),
),
Description: self.c.Tr.DropCommit,
@@ -439,6 +439,36 @@ func (self *LocalCommitsController) rewordEditor(commit *models.Commit) error {
func (self *LocalCommitsController) drop(selectedCommits []*models.Commit, startIdx int, endIdx int) error {
if self.isRebasing() {
+ groupedTodos := lo.GroupBy(selectedCommits, func(c *models.Commit) bool {
+ return c.Action == todo.UpdateRef
+ })
+ updateRefTodos := groupedTodos[true]
+ nonUpdateRefTodos := groupedTodos[false]
+
+ if len(updateRefTodos) > 0 {
+ return self.c.Confirm(types.ConfirmOpts{
+ Title: self.c.Tr.DropCommitTitle,
+ Prompt: self.c.Tr.DropUpdateRefPrompt,
+ HandleConfirm: func() error {
+ selectedIdx, rangeStartIdx, rangeSelectMode := self.context().GetSelectionRangeAndMode()
+
+ if err := self.c.Git().Rebase.DeleteUpdateRefTodos(updateRefTodos); err != nil {
+ return err
+ }
+
+ if selectedIdx > rangeStartIdx {
+ selectedIdx = utils.Max(selectedIdx-len(updateRefTodos), rangeStartIdx)
+ } else {
+ rangeStartIdx = utils.Max(rangeStartIdx-len(updateRefTodos), selectedIdx)
+ }
+
+ self.context().SetSelectionRangeAndMode(selectedIdx, rangeStartIdx, rangeSelectMode)
+
+ return self.updateTodos(todo.Drop, nonUpdateRefTodos)
+ },
+ })
+ }
+
return self.updateTodos(todo.Drop, selectedCommits)
}
@@ -1213,6 +1243,28 @@ func (self *LocalCommitsController) midRebaseMoveCommandEnabled(selectedCommits
return nil
}
+func (self *LocalCommitsController) canDropCommits(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason {
+ if !self.isRebasing() {
+ return nil
+ }
+
+ nonUpdateRefTodos := lo.Filter(selectedCommits, func(c *models.Commit, _ int) bool {
+ return c.Action != todo.UpdateRef
+ })
+
+ for _, commit := range nonUpdateRefTodos {
+ if !commit.IsTODO() {
+ return &types.DisabledReason{Text: self.c.Tr.MustSelectTodoCommits}
+ }
+
+ if !isChangeOfRebaseTodoAllowed(commit.Action) {
+ return &types.DisabledReason{Text: self.c.Tr.ChangingThisActionIsNotAllowed}
+ }
+ }
+
+ return nil
+}
+
// These actions represent standard things you might want to do with a commit,
// as opposed to TODO actions like 'merge', 'update-ref', etc.
var standardActions = []todo.TodoCommand{
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index 1ec3af2c1..d38bb1180 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -325,6 +325,7 @@ type TranslationSet struct {
AmendCommitPrompt string
DropCommitTitle string
DropCommitPrompt string
+ DropUpdateRefPrompt string
PullingStatus string
PushingStatus string
FetchingStatus string
@@ -1280,6 +1281,7 @@ func EnglishTranslationSet() TranslationSet {
AmendCommitPrompt: "Are you sure you want to amend this commit with your staged files?",
DropCommitTitle: "Drop commit",
DropCommitPrompt: "Are you sure you want to drop the selected commit(s)?",
+ DropUpdateRefPrompt: "Are you sure you want to delete the selected update-ref todo(s)? This is irreversible except by aborting the rebase.",
PullingStatus: "Pulling",
PushingStatus: "Pushing",
FetchingStatus: "Fetching",
diff --git a/pkg/integration/tests/interactive_rebase/delete_update_ref_todo.go b/pkg/integration/tests/interactive_rebase/delete_update_ref_todo.go
new file mode 100644
index 000000000..d08f518ec
--- /dev/null
+++ b/pkg/integration/tests/interactive_rebase/delete_update_ref_todo.go
@@ -0,0 +1,65 @@
+package interactive_rebase
+
+import (
+ "github.com/jesseduffield/lazygit/pkg/config"
+ . "github.com/jesseduffield/lazygit/pkg/integration/components"
+)
+
+var DeleteUpdateRefTodo = NewIntegrationTest(NewIntegrationTestArgs{
+ Description: "Delete an update-ref item from the rebase todo list",
+ ExtraCmdArgs: []string{},
+ Skip: false,
+ GitVersion: AtLeast("2.38.0"),
+ SetupConfig: func(config *config.AppConfig) {},
+ SetupRepo: func(shell *Shell) {
+ shell.
+ NewBranch("branch1").
+ CreateNCommits(3).
+ NewBranch("branch2").
+ CreateNCommitsStartingAt(3, 4)
+
+ shell.SetConfig("rebase.updateRefs", "true")
+ },
+ Run: func(t *TestDriver, keys config.KeybindingConfig) {
+ t.Views().Commits().
+ Focus().
+ NavigateToLine(Contains("commit 01")).
+ Press(keys.Universal.Edit).
+ Lines(
+ Contains("pick").Contains("CI commit 06"),
+ Contains("pick").Contains("CI commit 05"),
+ Contains("pick").Contains("CI commit 04"),
+ Contains("update-ref").Contains("branch1"),
+ Contains("pick").Contains("CI commit 03"),
+ Contains("pick").Contains("CI commit 02"),
+ Contains("CI ◯ <-- YOU ARE HERE --- commit 01"),
+ ).
+ NavigateToLine(Contains("update-ref")).
+ Press(keys.Universal.Remove).
+ Tap(func() {
+ t.ExpectPopup().Confirmation().
+ Title(Equals("Drop commit")).
+ Content(Contains("Are you sure you want to delete the selected update-ref todo(s)?")).
+ Confirm()
+ }).
+ Lines(
+ Contains("pick").Contains("CI commit 06"),
+ Contains("pick").Contains("CI commit 05"),
+ Contains("pick").Contains("CI commit 04"),
+ Contains("pick").Contains("CI commit 03").IsSelected(),
+ Contains("pick").Contains("CI commit 02"),
+ Contains("CI ◯ <-- YOU ARE HERE --- commit 01"),
+ ).
+ Tap(func() {
+ t.Common().ContinueRebase()
+ }).
+ Lines(
+ Contains("CI ◯ commit 06"),
+ Contains("CI ◯ commit 05"),
+ Contains("CI ◯ commit 04"),
+ Contains("CI ◯ commit 03"), // No start on this commit, so there's no branch head here
+ Contains("CI ◯ commit 02"),
+ Contains("CI ◯ commit 01"),
+ )
+ },
+})
diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go
index be463fc87..0a78d0193 100644
--- a/pkg/integration/tests/test_list.go
+++ b/pkg/integration/tests/test_list.go
@@ -164,6 +164,7 @@ var tests = []*components.IntegrationTest{
interactive_rebase.AmendHeadCommitDuringRebase,
interactive_rebase.AmendMerge,
interactive_rebase.AmendNonHeadCommitDuringRebase,
+ interactive_rebase.DeleteUpdateRefTodo,
interactive_rebase.DontShowBranchHeadsForTodoItems,
interactive_rebase.DropTodoCommitWithUpdateRef,
interactive_rebase.DropWithCustomCommentChar,
diff --git a/pkg/utils/rebase_todo.go b/pkg/utils/rebase_todo.go
index abd15b9dc..3403eef97 100644
--- a/pkg/utils/rebase_todo.go
+++ b/pkg/utils/rebase_todo.go
@@ -106,6 +106,33 @@ func PrependStrToTodoFile(filePath string, linesToPrepend []byte) error {
return os.WriteFile(filePath, linesToPrepend, 0o644)
}
+func DeleteTodos(fileName string, todosToDelete []Todo, commentChar byte) error {
+ todos, err := ReadRebaseTodoFile(fileName, commentChar)
+ if err != nil {
+ return err
+ }
+ rearrangedTodos, err := deleteTodos(todos, todosToDelete)
+ if err != nil {
+ return err
+ }
+ return WriteRebaseTodoFile(fileName, rearrangedTodos, commentChar)
+}
+
+func deleteTodos(todos []todo.Todo, todosToDelete []Todo) ([]todo.Todo, error) {
+ for _, todoToDelete := range todosToDelete {
+ idx, ok := findTodo(todos, todoToDelete)
+
+ if !ok {
+ // Should never happen
+ return []todo.Todo{}, fmt.Errorf("Todo %s not found in git-rebase-todo", todoToDelete.Sha)
+ }
+
+ todos = Remove(todos, idx)
+ }
+
+ return todos, nil
+}
+
func MoveTodosDown(fileName string, todosToMove []Todo, commentChar byte) error {
todos, err := ReadRebaseTodoFile(fileName, commentChar)
if err != nil {
diff --git a/pkg/utils/rebase_todo_test.go b/pkg/utils/rebase_todo_test.go
index 458b4ee14..fd87d7c7c 100644
--- a/pkg/utils/rebase_todo_test.go
+++ b/pkg/utils/rebase_todo_test.go
@@ -360,3 +360,58 @@ func TestRebaseCommands_moveFixupCommitDown(t *testing.T) {
})
}
}
+
+func TestRebaseCommands_deleteTodos(t *testing.T) {
+ scenarios := []struct {
+ name string
+ todos []todo.Todo
+ todosToDelete []Todo
+ expectedTodos []todo.Todo
+ expectedErr error
+ }{
+ {
+ name: "success",
+ todos: []todo.Todo{
+ {Command: todo.Pick, Commit: "1234"},
+ {Command: todo.UpdateRef, Ref: "refs/heads/some_branch"},
+ {Command: todo.Pick, Commit: "5678"},
+ {Command: todo.Pick, Commit: "abcd"},
+ },
+ todosToDelete: []Todo{
+ {Ref: "refs/heads/some_branch", Action: todo.UpdateRef},
+ {Sha: "abcd", Action: todo.Pick},
+ },
+ expectedTodos: []todo.Todo{
+ {Command: todo.Pick, Commit: "1234"},
+ {Command: todo.Pick, Commit: "5678"},
+ },
+ expectedErr: nil,
+ },
+ {
+ name: "failure",
+ todos: []todo.Todo{
+ {Command: todo.Pick, Commit: "1234"},
+ {Command: todo.Pick, Commit: "5678"},
+ },
+ todosToDelete: []Todo{
+ {Sha: "abcd", Action: todo.Pick},
+ },
+ expectedTodos: []todo.Todo{},
+ 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 := deleteTodos(scenario.todos, scenario.todosToDelete)
+
+ if scenario.expectedErr == nil {
+ assert.NoError(t, actualErr)
+ } else {
+ assert.EqualError(t, actualErr, scenario.expectedErr.Error())
+ }
+
+ assert.EqualValues(t, scenario.expectedTodos, actualTodos)
+ })
+ }
+}