mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-07-30 03:23:08 +03:00
Allow deleting update-ref todos
This commit is contained in:
@ -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 {
|
func (self *RebaseCommands) MoveTodosDown(commits []*models.Commit) error {
|
||||||
fileName := filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo")
|
fileName := filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo")
|
||||||
todosToMove := lo.Map(commits, func(commit *models.Commit, _ int) utils.Todo {
|
todosToMove := lo.Map(commits, func(commit *models.Commit, _ int) utils.Todo {
|
||||||
|
@ -106,7 +106,7 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
|
|||||||
Handler: self.withItemsRange(self.drop),
|
Handler: self.withItemsRange(self.drop),
|
||||||
GetDisabledReason: self.require(
|
GetDisabledReason: self.require(
|
||||||
self.itemRangeSelected(
|
self.itemRangeSelected(
|
||||||
self.midRebaseCommandEnabled,
|
self.canDropCommits,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Description: self.c.Tr.DropCommit,
|
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 {
|
func (self *LocalCommitsController) drop(selectedCommits []*models.Commit, startIdx int, endIdx int) error {
|
||||||
if self.isRebasing() {
|
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)
|
return self.updateTodos(todo.Drop, selectedCommits)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1213,6 +1243,28 @@ func (self *LocalCommitsController) midRebaseMoveCommandEnabled(selectedCommits
|
|||||||
return nil
|
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,
|
// These actions represent standard things you might want to do with a commit,
|
||||||
// as opposed to TODO actions like 'merge', 'update-ref', etc.
|
// as opposed to TODO actions like 'merge', 'update-ref', etc.
|
||||||
var standardActions = []todo.TodoCommand{
|
var standardActions = []todo.TodoCommand{
|
||||||
|
@ -325,6 +325,7 @@ type TranslationSet struct {
|
|||||||
AmendCommitPrompt string
|
AmendCommitPrompt string
|
||||||
DropCommitTitle string
|
DropCommitTitle string
|
||||||
DropCommitPrompt string
|
DropCommitPrompt string
|
||||||
|
DropUpdateRefPrompt string
|
||||||
PullingStatus string
|
PullingStatus string
|
||||||
PushingStatus string
|
PushingStatus string
|
||||||
FetchingStatus string
|
FetchingStatus string
|
||||||
@ -1280,6 +1281,7 @@ func EnglishTranslationSet() TranslationSet {
|
|||||||
AmendCommitPrompt: "Are you sure you want to amend this commit with your staged files?",
|
AmendCommitPrompt: "Are you sure you want to amend this commit with your staged files?",
|
||||||
DropCommitTitle: "Drop commit",
|
DropCommitTitle: "Drop commit",
|
||||||
DropCommitPrompt: "Are you sure you want to drop the selected commit(s)?",
|
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",
|
PullingStatus: "Pulling",
|
||||||
PushingStatus: "Pushing",
|
PushingStatus: "Pushing",
|
||||||
FetchingStatus: "Fetching",
|
FetchingStatus: "Fetching",
|
||||||
|
@ -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"),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
@ -164,6 +164,7 @@ var tests = []*components.IntegrationTest{
|
|||||||
interactive_rebase.AmendHeadCommitDuringRebase,
|
interactive_rebase.AmendHeadCommitDuringRebase,
|
||||||
interactive_rebase.AmendMerge,
|
interactive_rebase.AmendMerge,
|
||||||
interactive_rebase.AmendNonHeadCommitDuringRebase,
|
interactive_rebase.AmendNonHeadCommitDuringRebase,
|
||||||
|
interactive_rebase.DeleteUpdateRefTodo,
|
||||||
interactive_rebase.DontShowBranchHeadsForTodoItems,
|
interactive_rebase.DontShowBranchHeadsForTodoItems,
|
||||||
interactive_rebase.DropTodoCommitWithUpdateRef,
|
interactive_rebase.DropTodoCommitWithUpdateRef,
|
||||||
interactive_rebase.DropWithCustomCommentChar,
|
interactive_rebase.DropWithCustomCommentChar,
|
||||||
|
@ -106,6 +106,33 @@ 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 {
|
||||||
|
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 {
|
func MoveTodosDown(fileName string, todosToMove []Todo, commentChar byte) error {
|
||||||
todos, err := ReadRebaseTodoFile(fileName, commentChar)
|
todos, err := ReadRebaseTodoFile(fileName, commentChar)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user