mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-01-10 04:07:18 +02:00
Support range select for rebase actions (#3232)
- **PR Description** Adds support for range select in rebase actions both mid-rebase and outside a rebase i.e.: * pick * drop * fixup * squash * move up * move down Also includes a refactor to support a withItems wrapper for keybinding handlers that support a range of items to be selected. TODO: * [x] Add integration tests * [x] Add some LogAction calls (some are currently commented out) - **Please check if the PR fulfills these requirements** * [x] Cheatsheets are up-to-date (run `go generate ./...`) * [x] Code has been formatted (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting)) * [x] Tests have been added/updated (see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md) for the integration test guide) * [x] Text is internationalised (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation)) * [x] Docs (specifically `docs/Config.md`) have been updated if necessary * [x] You've read through your own file changes for silly mistakes etc <!-- Be sure to name your PR with an imperative e.g. 'Add worktrees view' see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for examples -->
This commit is contained in:
commit
5511cc170e
@ -34,8 +34,8 @@ const (
|
||||
|
||||
DaemonKindExitImmediately
|
||||
DaemonKindCherryPick
|
||||
DaemonKindMoveTodoUp
|
||||
DaemonKindMoveTodoDown
|
||||
DaemonKindMoveTodosUp
|
||||
DaemonKindMoveTodosDown
|
||||
DaemonKindInsertBreak
|
||||
DaemonKindChangeTodoActions
|
||||
DaemonKindMoveFixupCommitDown
|
||||
@ -56,8 +56,8 @@ func getInstruction() Instruction {
|
||||
DaemonKindCherryPick: deserializeInstruction[*CherryPickCommitsInstruction],
|
||||
DaemonKindChangeTodoActions: deserializeInstruction[*ChangeTodoActionsInstruction],
|
||||
DaemonKindMoveFixupCommitDown: deserializeInstruction[*MoveFixupCommitDownInstruction],
|
||||
DaemonKindMoveTodoUp: deserializeInstruction[*MoveTodoUpInstruction],
|
||||
DaemonKindMoveTodoDown: deserializeInstruction[*MoveTodoDownInstruction],
|
||||
DaemonKindMoveTodosUp: deserializeInstruction[*MoveTodosUpInstruction],
|
||||
DaemonKindMoveTodosDown: deserializeInstruction[*MoveTodosDownInstruction],
|
||||
DaemonKindInsertBreak: deserializeInstruction[*InsertBreakInstruction],
|
||||
}
|
||||
|
||||
@ -208,13 +208,15 @@ func (self *ChangeTodoActionsInstruction) SerializedInstructions() string {
|
||||
|
||||
func (self *ChangeTodoActionsInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
for _, c := range self.Changes {
|
||||
if err := utils.EditRebaseTodo(path, c.Sha, todo.Pick, c.NewAction, getCommentChar()); err != nil {
|
||||
return err
|
||||
changes := lo.Map(self.Changes, func(c ChangeTodoAction, _ int) utils.TodoChange {
|
||||
return utils.TodoChange{
|
||||
Sha: c.Sha,
|
||||
OldAction: todo.Pick,
|
||||
NewAction: c.NewAction,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
return utils.EditRebaseTodo(path, changes, getCommentChar())
|
||||
})
|
||||
}
|
||||
|
||||
@ -247,51 +249,65 @@ func (self *MoveFixupCommitDownInstruction) run(common *common.Common) error {
|
||||
})
|
||||
}
|
||||
|
||||
type MoveTodoUpInstruction struct {
|
||||
Sha string
|
||||
type MoveTodosUpInstruction struct {
|
||||
Shas []string
|
||||
}
|
||||
|
||||
func NewMoveTodoUpInstruction(sha string) Instruction {
|
||||
return &MoveTodoUpInstruction{
|
||||
Sha: sha,
|
||||
func NewMoveTodosUpInstruction(shas []string) Instruction {
|
||||
return &MoveTodosUpInstruction{
|
||||
Shas: shas,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MoveTodoUpInstruction) Kind() DaemonKind {
|
||||
return DaemonKindMoveTodoUp
|
||||
func (self *MoveTodosUpInstruction) Kind() DaemonKind {
|
||||
return DaemonKindMoveTodosUp
|
||||
}
|
||||
|
||||
func (self *MoveTodoUpInstruction) SerializedInstructions() string {
|
||||
func (self *MoveTodosUpInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *MoveTodoUpInstruction) run(common *common.Common) error {
|
||||
func (self *MoveTodosUpInstruction) run(common *common.Common) error {
|
||||
todosToMove := lo.Map(self.Shas, func(sha string, _ int) utils.Todo {
|
||||
return utils.Todo{
|
||||
Sha: sha,
|
||||
Action: todo.Pick,
|
||||
}
|
||||
})
|
||||
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.MoveTodoUp(path, self.Sha, todo.Pick, getCommentChar())
|
||||
return utils.MoveTodosUp(path, todosToMove, getCommentChar())
|
||||
})
|
||||
}
|
||||
|
||||
type MoveTodoDownInstruction struct {
|
||||
Sha string
|
||||
type MoveTodosDownInstruction struct {
|
||||
Shas []string
|
||||
}
|
||||
|
||||
func NewMoveTodoDownInstruction(sha string) Instruction {
|
||||
return &MoveTodoDownInstruction{
|
||||
Sha: sha,
|
||||
func NewMoveTodosDownInstruction(shas []string) Instruction {
|
||||
return &MoveTodosDownInstruction{
|
||||
Shas: shas,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MoveTodoDownInstruction) Kind() DaemonKind {
|
||||
return DaemonKindMoveTodoDown
|
||||
func (self *MoveTodosDownInstruction) Kind() DaemonKind {
|
||||
return DaemonKindMoveTodosDown
|
||||
}
|
||||
|
||||
func (self *MoveTodoDownInstruction) SerializedInstructions() string {
|
||||
func (self *MoveTodosDownInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *MoveTodoDownInstruction) run(common *common.Common) error {
|
||||
func (self *MoveTodosDownInstruction) run(common *common.Common) error {
|
||||
todosToMove := lo.Map(self.Shas, func(sha string, _ int) utils.Todo {
|
||||
return utils.Todo{
|
||||
Sha: sha,
|
||||
Action: todo.Pick,
|
||||
}
|
||||
})
|
||||
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.MoveTodoDown(path, self.Sha, todo.Pick, getCommentChar())
|
||||
return utils.MoveTodosDown(path, todosToMove, getCommentChar())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -105,58 +105,49 @@ func (self *RebaseCommands) GenericAmend(commits []*models.Commit, index int, f
|
||||
return self.ContinueRebase()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) MoveCommitDown(commits []*models.Commit, index int) error {
|
||||
baseShaOrRoot := getBaseShaOrRoot(commits, index+2)
|
||||
func (self *RebaseCommands) MoveCommitsDown(commits []*models.Commit, startIdx int, endIdx int) error {
|
||||
baseShaOrRoot := getBaseShaOrRoot(commits, endIdx+2)
|
||||
|
||||
sha := commits[index].Sha
|
||||
|
||||
msg := utils.ResolvePlaceholderString(
|
||||
self.Tr.Log.MoveCommitDown,
|
||||
map[string]string{
|
||||
"shortSha": utils.ShortSha(sha),
|
||||
},
|
||||
)
|
||||
self.os.LogCommand(msg, false)
|
||||
shas := lo.Map(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) string {
|
||||
return commit.Sha
|
||||
})
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: baseShaOrRoot,
|
||||
instruction: daemon.NewMoveTodoDownInstruction(sha),
|
||||
instruction: daemon.NewMoveTodosDownInstruction(shas),
|
||||
overrideEditor: true,
|
||||
}).Run()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) MoveCommitUp(commits []*models.Commit, index int) error {
|
||||
baseShaOrRoot := getBaseShaOrRoot(commits, index+1)
|
||||
func (self *RebaseCommands) MoveCommitsUp(commits []*models.Commit, startIdx int, endIdx int) error {
|
||||
baseShaOrRoot := getBaseShaOrRoot(commits, endIdx+1)
|
||||
|
||||
sha := commits[index].Sha
|
||||
|
||||
msg := utils.ResolvePlaceholderString(
|
||||
self.Tr.Log.MoveCommitUp,
|
||||
map[string]string{
|
||||
"shortSha": utils.ShortSha(sha),
|
||||
},
|
||||
)
|
||||
self.os.LogCommand(msg, false)
|
||||
shas := lo.Map(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) string {
|
||||
return commit.Sha
|
||||
})
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: baseShaOrRoot,
|
||||
instruction: daemon.NewMoveTodoUpInstruction(sha),
|
||||
instruction: daemon.NewMoveTodosUpInstruction(shas),
|
||||
overrideEditor: true,
|
||||
}).Run()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index int, action todo.TodoCommand) error {
|
||||
baseIndex := index + 1
|
||||
func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, startIdx int, endIdx int, action todo.TodoCommand) error {
|
||||
baseIndex := endIdx + 1
|
||||
if action == todo.Squash || action == todo.Fixup {
|
||||
baseIndex++
|
||||
}
|
||||
|
||||
baseShaOrRoot := getBaseShaOrRoot(commits, baseIndex)
|
||||
|
||||
changes := []daemon.ChangeTodoAction{{
|
||||
Sha: commits[index].Sha,
|
||||
NewAction: action,
|
||||
}}
|
||||
changes := lo.Map(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) daemon.ChangeTodoAction {
|
||||
return daemon.ChangeTodoAction{
|
||||
Sha: commit.Sha,
|
||||
NewAction: action,
|
||||
}
|
||||
})
|
||||
|
||||
self.os.LogCommand(logTodoChanges(changes), false)
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
@ -200,7 +191,7 @@ func logTodoChanges(changes []daemon.ChangeTodoAction) string {
|
||||
changeTodoStr := strings.Join(lo.Map(changes, func(c daemon.ChangeTodoAction, _ int) string {
|
||||
return fmt.Sprintf("%s:%s", c.Sha, c.NewAction)
|
||||
}), "\n")
|
||||
return fmt.Sprintf("Changing TODO actions: %s", changeTodoStr)
|
||||
return fmt.Sprintf("Changing TODO actions:\n%s", changeTodoStr)
|
||||
}
|
||||
|
||||
type PrepareInteractiveRebaseCommandOpts struct {
|
||||
@ -281,22 +272,45 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e
|
||||
}).Run()
|
||||
}
|
||||
|
||||
// EditRebaseTodo sets the action for a given rebase commit in the git-rebase-todo file
|
||||
func (self *RebaseCommands) EditRebaseTodo(commit *models.Commit, action todo.TodoCommand) error {
|
||||
// Sets the action for the given commits in the git-rebase-todo file
|
||||
func (self *RebaseCommands) EditRebaseTodo(commits []*models.Commit, action todo.TodoCommand) error {
|
||||
commitsWithAction := lo.Map(commits, func(commit *models.Commit, _ int) utils.TodoChange {
|
||||
return utils.TodoChange{
|
||||
Sha: commit.Sha,
|
||||
OldAction: commit.Action,
|
||||
NewAction: action,
|
||||
}
|
||||
})
|
||||
|
||||
return utils.EditRebaseTodo(
|
||||
filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo"), commit.Sha, commit.Action, action, self.config.GetCoreCommentChar())
|
||||
filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo"),
|
||||
commitsWithAction,
|
||||
self.config.GetCoreCommentChar(),
|
||||
)
|
||||
}
|
||||
|
||||
// MoveTodoDown moves a rebase todo item down by one position
|
||||
func (self *RebaseCommands) MoveTodoDown(commit *models.Commit) error {
|
||||
func (self *RebaseCommands) MoveTodosDown(commits []*models.Commit) error {
|
||||
fileName := filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo")
|
||||
return utils.MoveTodoDown(fileName, commit.Sha, commit.Action, self.config.GetCoreCommentChar())
|
||||
todosToMove := lo.Map(commits, func(commit *models.Commit, _ int) utils.Todo {
|
||||
return utils.Todo{
|
||||
Sha: commit.Sha,
|
||||
Action: commit.Action,
|
||||
}
|
||||
})
|
||||
|
||||
return utils.MoveTodosDown(fileName, todosToMove, self.config.GetCoreCommentChar())
|
||||
}
|
||||
|
||||
// MoveTodoDown moves a rebase todo item down by one position
|
||||
func (self *RebaseCommands) MoveTodoUp(commit *models.Commit) error {
|
||||
func (self *RebaseCommands) MoveTodosUp(commits []*models.Commit) error {
|
||||
fileName := filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo")
|
||||
return utils.MoveTodoUp(fileName, commit.Sha, commit.Action, self.config.GetCoreCommentChar())
|
||||
todosToMove := lo.Map(commits, func(commit *models.Commit, _ int) utils.Todo {
|
||||
return utils.Todo{
|
||||
Sha: commit.Sha,
|
||||
Action: commit.Action,
|
||||
}
|
||||
})
|
||||
|
||||
return utils.MoveTodosUp(fileName, todosToMove, self.config.GetCoreCommentChar())
|
||||
}
|
||||
|
||||
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one
|
||||
|
@ -59,15 +59,6 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *BranchesContext) GetSelectedItemId() string {
|
||||
item := self.GetSelected()
|
||||
if item == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
||||
func (self *BranchesContext) GetSelectedRef() types.Ref {
|
||||
branch := self.GetSelected()
|
||||
if branch == nil {
|
||||
|
@ -72,15 +72,6 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (self *CommitFilesContext) GetSelectedItemId() string {
|
||||
item := self.GetSelected()
|
||||
if item == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
||||
func (self *CommitFilesContext) GetDiffTerminals() []string {
|
||||
return []string{self.GetRef().RefName()}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
package context
|
||||
|
||||
type FilteredListViewModel[T any] struct {
|
||||
type FilteredListViewModel[T HasID] struct {
|
||||
*FilteredList[T]
|
||||
*ListViewModel[T]
|
||||
*SearchHistory
|
||||
}
|
||||
|
||||
func NewFilteredListViewModel[T any](getList func() []T, getFilterFields func(T) []string) *FilteredListViewModel[T] {
|
||||
func NewFilteredListViewModel[T HasID](getList func() []T, getFilterFields func(T) []string) *FilteredListViewModel[T] {
|
||||
filteredList := NewFilteredList(getList, getFilterFields)
|
||||
|
||||
self := &FilteredListViewModel[T]{
|
||||
|
@ -9,10 +9,17 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// wrapping string in my own type to give it an ID method which is required for list items
|
||||
type mystring string
|
||||
|
||||
func (self mystring) ID() string {
|
||||
return string(self)
|
||||
}
|
||||
|
||||
func TestListRenderer_renderLines(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
modelStrings []string
|
||||
modelStrings []mystring
|
||||
nonModelIndices []int
|
||||
startIdx int
|
||||
endIdx int
|
||||
@ -20,7 +27,7 @@ func TestListRenderer_renderLines(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "Render whole list",
|
||||
modelStrings: []string{"a", "b", "c"},
|
||||
modelStrings: []mystring{"a", "b", "c"},
|
||||
startIdx: 0,
|
||||
endIdx: 3,
|
||||
expectedOutput: `
|
||||
@ -30,7 +37,7 @@ func TestListRenderer_renderLines(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Partial list, beginning",
|
||||
modelStrings: []string{"a", "b", "c"},
|
||||
modelStrings: []mystring{"a", "b", "c"},
|
||||
startIdx: 0,
|
||||
endIdx: 2,
|
||||
expectedOutput: `
|
||||
@ -39,7 +46,7 @@ func TestListRenderer_renderLines(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Partial list, end",
|
||||
modelStrings: []string{"a", "b", "c"},
|
||||
modelStrings: []mystring{"a", "b", "c"},
|
||||
startIdx: 1,
|
||||
endIdx: 3,
|
||||
expectedOutput: `
|
||||
@ -48,7 +55,7 @@ func TestListRenderer_renderLines(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Pass an endIdx greater than the model length",
|
||||
modelStrings: []string{"a", "b", "c"},
|
||||
modelStrings: []mystring{"a", "b", "c"},
|
||||
startIdx: 2,
|
||||
endIdx: 5,
|
||||
expectedOutput: `
|
||||
@ -56,7 +63,7 @@ func TestListRenderer_renderLines(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Whole list with section headers",
|
||||
modelStrings: []string{"a", "b", "c"},
|
||||
modelStrings: []mystring{"a", "b", "c"},
|
||||
nonModelIndices: []int{1, 3},
|
||||
startIdx: 0,
|
||||
endIdx: 5,
|
||||
@ -69,7 +76,7 @@ func TestListRenderer_renderLines(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Multiple consecutive headers",
|
||||
modelStrings: []string{"a", "b", "c"},
|
||||
modelStrings: []mystring{"a", "b", "c"},
|
||||
nonModelIndices: []int{0, 0, 2, 2, 2},
|
||||
startIdx: 0,
|
||||
endIdx: 8,
|
||||
@ -85,7 +92,7 @@ func TestListRenderer_renderLines(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Partial list with headers, beginning",
|
||||
modelStrings: []string{"a", "b", "c"},
|
||||
modelStrings: []mystring{"a", "b", "c"},
|
||||
nonModelIndices: []int{1, 3},
|
||||
startIdx: 0,
|
||||
endIdx: 3,
|
||||
@ -96,7 +103,7 @@ func TestListRenderer_renderLines(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Partial list with headers, end (beyond end index)",
|
||||
modelStrings: []string{"a", "b", "c"},
|
||||
modelStrings: []mystring{"a", "b", "c"},
|
||||
nonModelIndices: []int{1, 3},
|
||||
startIdx: 2,
|
||||
endIdx: 7,
|
||||
@ -108,7 +115,7 @@ func TestListRenderer_renderLines(t *testing.T) {
|
||||
}
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
viewModel := NewListViewModel[string](func() []string { return s.modelStrings })
|
||||
viewModel := NewListViewModel[mystring](func() []mystring { return s.modelStrings })
|
||||
var getNonModelItems func() []*NonModelItem
|
||||
if s.nonModelIndices != nil {
|
||||
getNonModelItems = func() []*NonModelItem {
|
||||
@ -124,7 +131,7 @@ func TestListRenderer_renderLines(t *testing.T) {
|
||||
list: viewModel,
|
||||
getDisplayStrings: func(startIdx int, endIdx int) [][]string {
|
||||
return lo.Map(s.modelStrings[startIdx:endIdx],
|
||||
func(s string, _ int) []string { return []string{s} })
|
||||
func(s mystring, _ int) []string { return []string{string(s)} })
|
||||
},
|
||||
getNonModelItems: getNonModelItems,
|
||||
}
|
||||
@ -138,6 +145,12 @@ func TestListRenderer_renderLines(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type myint int
|
||||
|
||||
func (self myint) ID() string {
|
||||
return fmt.Sprint(int(self))
|
||||
}
|
||||
|
||||
func TestListRenderer_ModelIndexToViewIndex_and_back(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
@ -222,8 +235,8 @@ func TestListRenderer_ModelIndexToViewIndex_and_back(t *testing.T) {
|
||||
assert.Equal(t, len(s.modelIndices), len(s.expectedViewIndices))
|
||||
assert.Equal(t, len(s.viewIndices), len(s.expectedModelIndices))
|
||||
|
||||
modelInts := lo.Range(s.numModelItems)
|
||||
viewModel := NewListViewModel[int](func() []int { return modelInts })
|
||||
modelInts := lo.Map(lo.Range(s.numModelItems), func(i int, _ int) myint { return myint(i) })
|
||||
viewModel := NewListViewModel[myint](func() []myint { return modelInts })
|
||||
var getNonModelItems func() []*NonModelItem
|
||||
if s.nonModelIndices != nil {
|
||||
getNonModelItems = func() []*NonModelItem {
|
||||
@ -236,7 +249,7 @@ func TestListRenderer_ModelIndexToViewIndex_and_back(t *testing.T) {
|
||||
list: viewModel,
|
||||
getDisplayStrings: func(startIdx int, endIdx int) [][]string {
|
||||
return lo.Map(modelInts[startIdx:endIdx],
|
||||
func(i int, _ int) []string { return []string{fmt.Sprint(i)} })
|
||||
func(i myint, _ int) []string { return []string{fmt.Sprint(i)} })
|
||||
},
|
||||
getNonModelItems: getNonModelItems,
|
||||
}
|
||||
|
@ -3,14 +3,19 @@ package context
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type ListViewModel[T any] struct {
|
||||
type HasID interface {
|
||||
ID() string
|
||||
}
|
||||
|
||||
type ListViewModel[T HasID] struct {
|
||||
*traits.ListCursor
|
||||
getModel func() []T
|
||||
}
|
||||
|
||||
func NewListViewModel[T any](getModel func() []T) *ListViewModel[T] {
|
||||
func NewListViewModel[T HasID](getModel func() []T) *ListViewModel[T] {
|
||||
self := &ListViewModel[T]{
|
||||
getModel: getModel,
|
||||
}
|
||||
@ -32,6 +37,34 @@ func (self *ListViewModel[T]) GetSelected() T {
|
||||
return self.getModel()[self.GetSelectedLineIdx()]
|
||||
}
|
||||
|
||||
func (self *ListViewModel[T]) GetSelectedItemId() string {
|
||||
if self.Len() == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return self.GetSelected().ID()
|
||||
}
|
||||
|
||||
func (self *ListViewModel[T]) GetSelectedItems() ([]T, int, int) {
|
||||
if self.Len() == 0 {
|
||||
return nil, -1, -1
|
||||
}
|
||||
|
||||
startIdx, endIdx := self.GetSelectionRange()
|
||||
|
||||
return self.getModel()[startIdx : endIdx+1], startIdx, endIdx
|
||||
}
|
||||
|
||||
func (self *ListViewModel[T]) GetSelectedItemIds() ([]string, int, int) {
|
||||
selectedItems, startIdx, endIdx := self.GetSelectedItems()
|
||||
|
||||
ids := lo.Map(selectedItems, func(item T, _ int) string {
|
||||
return item.ID()
|
||||
})
|
||||
|
||||
return ids, startIdx, endIdx
|
||||
}
|
||||
|
||||
func (self *ListViewModel[T]) GetItems() []T {
|
||||
return self.getModel()
|
||||
}
|
||||
|
@ -92,15 +92,6 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (self *LocalCommitsContext) GetSelectedItemId() string {
|
||||
item := self.GetSelected()
|
||||
if item == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
||||
type LocalCommitsViewModel struct {
|
||||
*ListViewModel[*models.Commit]
|
||||
|
||||
|
@ -45,16 +45,6 @@ func NewMenuContext(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove this thing.
|
||||
func (self *MenuContext) GetSelectedItemId() string {
|
||||
item := self.GetSelected()
|
||||
if item == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return item.Label
|
||||
}
|
||||
|
||||
type MenuViewModel struct {
|
||||
c *ContextCommon
|
||||
menuItems []*types.MenuItem
|
||||
|
@ -59,15 +59,6 @@ func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ReflogCommitsContext) GetSelectedItemId() string {
|
||||
item := self.GetSelected()
|
||||
if item == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
||||
func (self *ReflogCommitsContext) CanRebase() bool {
|
||||
return false
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type RemoteBranchesContext struct {
|
||||
@ -53,15 +54,6 @@ func NewRemoteBranchesContext(
|
||||
}
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesContext) GetSelectedItemId() string {
|
||||
item := self.GetSelected()
|
||||
if item == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesContext) GetSelectedRef() types.Ref {
|
||||
remoteBranch := self.GetSelected()
|
||||
if remoteBranch == nil {
|
||||
@ -70,6 +62,16 @@ func (self *RemoteBranchesContext) GetSelectedRef() types.Ref {
|
||||
return remoteBranch
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesContext) GetSelectedRefs() ([]types.Ref, int, int) {
|
||||
items, startIdx, endIdx := self.GetSelectedItems()
|
||||
|
||||
refs := lo.Map(items, func(item *models.RemoteBranch, _ int) types.Ref {
|
||||
return item
|
||||
})
|
||||
|
||||
return refs, startIdx, endIdx
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
|
@ -47,15 +47,6 @@ func NewRemotesContext(c *ContextCommon) *RemotesContext {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *RemotesContext) GetSelectedItemId() string {
|
||||
item := self.GetSelected()
|
||||
if item == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
||||
func (self *RemotesContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
|
@ -49,15 +49,6 @@ func NewStashContext(
|
||||
}
|
||||
}
|
||||
|
||||
func (self *StashContext) GetSelectedItemId() string {
|
||||
item := self.GetSelected()
|
||||
if item == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
||||
func (self *StashContext) CanRebase() bool {
|
||||
return false
|
||||
}
|
||||
|
@ -175,15 +175,6 @@ func (self *SubCommitsViewModel) GetShowBranchHeads() bool {
|
||||
return self.showBranchHeads
|
||||
}
|
||||
|
||||
func (self *SubCommitsContext) GetSelectedItemId() string {
|
||||
item := self.GetSelected()
|
||||
if item == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
||||
func (self *SubCommitsContext) CanRebase() bool {
|
||||
return false
|
||||
}
|
||||
|
@ -43,12 +43,3 @@ func NewSubmodulesContext(c *ContextCommon) *SubmodulesContext {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SubmodulesContext) GetSelectedItemId() string {
|
||||
item := self.GetSelected()
|
||||
if item == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
@ -63,15 +63,6 @@ func NewSuggestionsContext(
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SuggestionsContext) GetSelectedItemId() string {
|
||||
item := self.GetSelected()
|
||||
if item == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return item.Value
|
||||
}
|
||||
|
||||
func (self *SuggestionsContext) SetSuggestions(suggestions []*types.Suggestion) {
|
||||
self.State.Suggestions = suggestions
|
||||
self.SetSelection(0)
|
||||
|
@ -52,15 +52,6 @@ func NewTagsContext(
|
||||
}
|
||||
}
|
||||
|
||||
func (self *TagsContext) GetSelectedItemId() string {
|
||||
item := self.GetSelected()
|
||||
if item == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
||||
func (self *TagsContext) GetSelectedRef() types.Ref {
|
||||
tag := self.GetSelected()
|
||||
if tag == nil {
|
||||
|
@ -58,12 +58,3 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (self *WorkingTreeContext) GetSelectedItemId() string {
|
||||
item := self.GetSelected()
|
||||
if item == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
@ -46,12 +46,3 @@ func NewWorktreesContext(c *ContextCommon) *WorktreesContext {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *WorktreesContext) GetSelectedItemId() string {
|
||||
item := self.GetSelected()
|
||||
if item == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ type ContainsCommits interface {
|
||||
types.Context
|
||||
types.IListContext
|
||||
GetSelected() *models.Commit
|
||||
GetSelectedItems() ([]*models.Commit, int, int)
|
||||
GetCommits() []*models.Commit
|
||||
GetSelectedLineIdx() int
|
||||
}
|
||||
@ -36,6 +37,7 @@ func NewBasicCommitsController(c *ControllerCommon, context ContainsCommits) *Ba
|
||||
c,
|
||||
context,
|
||||
context.GetSelected,
|
||||
context.GetSelectedItems,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ func NewBisectController(
|
||||
c,
|
||||
c.Contexts().LocalCommits,
|
||||
c.Contexts().LocalCommits.GetSelected,
|
||||
c.Contexts().LocalCommits.GetSelectedItems,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ func NewBranchesController(
|
||||
c,
|
||||
c.Contexts().Branches,
|
||||
c.Contexts().Branches.GetSelected,
|
||||
c.Contexts().Branches.GetSelectedItems,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ func NewCommitFilesController(
|
||||
c,
|
||||
c.Contexts().CommitFiles,
|
||||
c.Contexts().CommitFiles.GetSelected,
|
||||
c.Contexts().CommitFiles.GetSelectedItems,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ func NewFilesController(
|
||||
c,
|
||||
c.Contexts().Files,
|
||||
c.Contexts().Files.GetSelected,
|
||||
c.Contexts().Files.GetSelectedItems,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ func NewFilesRemoveController(
|
||||
c,
|
||||
c.Contexts().Files,
|
||||
c.Contexts().Files.GetSelected,
|
||||
c.Contexts().Files.GetSelectedItems,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ func NewGitFlowController(
|
||||
c,
|
||||
c.Contexts().Branches,
|
||||
c.Contexts().Branches.GetSelected,
|
||||
c.Contexts().Branches.GetSelectedItems,
|
||||
),
|
||||
c: c,
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
@ -80,6 +82,19 @@ func (self *MergeAndRebaseHelper) genericMergeCommand(command string) error {
|
||||
}
|
||||
|
||||
self.c.LogAction(fmt.Sprintf("Merge/Rebase: %s", command))
|
||||
if status == enums.REBASE_MODE_REBASING {
|
||||
todoFile, err := os.ReadFile(
|
||||
filepath.Join(self.c.Git().RepoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo"),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
self.c.LogCommand(string(todoFile), false)
|
||||
}
|
||||
}
|
||||
|
||||
commandType := ""
|
||||
switch status {
|
||||
|
@ -6,20 +6,23 @@ import "github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
// ensuring a single item is selected, etc.
|
||||
|
||||
type ListControllerTrait[T comparable] struct {
|
||||
c *ControllerCommon
|
||||
context types.IListContext
|
||||
getSelected func() T
|
||||
c *ControllerCommon
|
||||
context types.IListContext
|
||||
getSelectedItem func() T
|
||||
getSelectedItems func() ([]T, int, int)
|
||||
}
|
||||
|
||||
func NewListControllerTrait[T comparable](
|
||||
c *ControllerCommon,
|
||||
context types.IListContext,
|
||||
getSelected func() T,
|
||||
getSelectedItems func() ([]T, int, int),
|
||||
) *ListControllerTrait[T] {
|
||||
return &ListControllerTrait[T]{
|
||||
c: c,
|
||||
context: context,
|
||||
getSelected: getSelected,
|
||||
c: c,
|
||||
context: context,
|
||||
getSelectedItem: getSelected,
|
||||
getSelectedItems: getSelectedItems,
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,7 +50,7 @@ func (self *ListControllerTrait[T]) singleItemSelected(callbacks ...func(T) *typ
|
||||
}
|
||||
|
||||
var zeroValue T
|
||||
item := self.getSelected()
|
||||
item := self.getSelectedItem()
|
||||
if item == zeroValue {
|
||||
return &types.DisabledReason{Text: self.c.Tr.NoItemSelected}
|
||||
}
|
||||
@ -62,11 +65,46 @@ func (self *ListControllerTrait[T]) singleItemSelected(callbacks ...func(T) *typ
|
||||
}
|
||||
}
|
||||
|
||||
// Ensures that at least one item is selected.
|
||||
func (self *ListControllerTrait[T]) itemRangeSelected(callbacks ...func([]T, int, int) *types.DisabledReason) func() *types.DisabledReason {
|
||||
return func() *types.DisabledReason {
|
||||
items, startIdx, endIdx := self.getSelectedItems()
|
||||
if len(items) == 0 {
|
||||
return &types.DisabledReason{Text: self.c.Tr.NoItemSelected}
|
||||
}
|
||||
|
||||
for _, callback := range callbacks {
|
||||
if reason := callback(items, startIdx, endIdx); reason != nil {
|
||||
return reason
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ListControllerTrait[T]) itemsSelected(callbacks ...func([]T) *types.DisabledReason) func() *types.DisabledReason { //nolint:unused
|
||||
return func() *types.DisabledReason {
|
||||
items, _, _ := self.getSelectedItems()
|
||||
if len(items) == 0 {
|
||||
return &types.DisabledReason{Text: self.c.Tr.NoItemSelected}
|
||||
}
|
||||
|
||||
for _, callback := range callbacks {
|
||||
if reason := callback(items); reason != nil {
|
||||
return reason
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Passes the selected item to the callback. Used for handler functions.
|
||||
func (self *ListControllerTrait[T]) withItem(callback func(T) error) func() error {
|
||||
return func() error {
|
||||
var zeroValue T
|
||||
commit := self.getSelected()
|
||||
commit := self.getSelectedItem()
|
||||
if commit == zeroValue {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoItemSelected)
|
||||
}
|
||||
@ -75,12 +113,35 @@ func (self *ListControllerTrait[T]) withItem(callback func(T) error) func() erro
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ListControllerTrait[T]) withItems(callback func([]T) error) func() error {
|
||||
return func() error {
|
||||
items, _, _ := self.getSelectedItems()
|
||||
if len(items) == 0 {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoItemSelected)
|
||||
}
|
||||
|
||||
return callback(items)
|
||||
}
|
||||
}
|
||||
|
||||
// like withItems but also passes the start and end index of the selection
|
||||
func (self *ListControllerTrait[T]) withItemsRange(callback func([]T, int, int) error) func() error {
|
||||
return func() error {
|
||||
items, startIdx, endIdx := self.getSelectedItems()
|
||||
if len(items) == 0 {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoItemSelected)
|
||||
}
|
||||
|
||||
return callback(items, startIdx, endIdx)
|
||||
}
|
||||
}
|
||||
|
||||
// Like withItem, but doesn't show an error message if no item is selected.
|
||||
// Use this for click actions (it's a no-op to click empty space)
|
||||
func (self *ListControllerTrait[T]) withItemGraceful(callback func(T) error) func() error {
|
||||
return func() error {
|
||||
var zeroValue T
|
||||
commit := self.getSelected()
|
||||
commit := self.getSelectedItem()
|
||||
if commit == zeroValue {
|
||||
return nil
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ func NewLocalCommitsController(
|
||||
c,
|
||||
c.Contexts().LocalCommits,
|
||||
c.Contexts().LocalCommits.GetSelected,
|
||||
c.Contexts().LocalCommits.GetSelectedItems,
|
||||
),
|
||||
}
|
||||
}
|
||||
@ -55,17 +56,23 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
outsideFilterModeBindings := []*types.Binding{
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Commits.SquashDown),
|
||||
Handler: self.withItem(self.squashDown),
|
||||
Handler: self.withItemsRange(self.squashDown),
|
||||
GetDisabledReason: self.require(
|
||||
self.singleItemSelected(self.getDisabledReasonForSquashDown),
|
||||
self.itemRangeSelected(
|
||||
self.midRebaseCommandEnabled,
|
||||
self.canSquashOrFixup,
|
||||
),
|
||||
),
|
||||
Description: self.c.Tr.SquashDown,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Commits.MarkCommitAsFixup),
|
||||
Handler: self.withItem(self.fixup),
|
||||
Handler: self.withItemsRange(self.fixup),
|
||||
GetDisabledReason: self.require(
|
||||
self.singleItemSelected(self.getDisabledReasonForFixup),
|
||||
self.itemRangeSelected(
|
||||
self.midRebaseCommandEnabled,
|
||||
self.canSquashOrFixup,
|
||||
),
|
||||
),
|
||||
Description: self.c.Tr.FixupCommit,
|
||||
},
|
||||
@ -73,7 +80,7 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
Key: opts.GetKey(opts.Config.Commits.RenameCommit),
|
||||
Handler: self.withItem(self.reword),
|
||||
GetDisabledReason: self.require(
|
||||
self.singleItemSelected(self.rebaseCommandEnabled(todo.Reword)),
|
||||
self.singleItemSelected(self.rewordEnabled),
|
||||
),
|
||||
Description: self.c.Tr.RewordCommit,
|
||||
},
|
||||
@ -81,23 +88,26 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
Key: opts.GetKey(opts.Config.Commits.RenameCommitWithEditor),
|
||||
Handler: self.withItem(self.rewordEditor),
|
||||
GetDisabledReason: self.require(
|
||||
self.singleItemSelected(self.rebaseCommandEnabled(todo.Reword)),
|
||||
self.singleItemSelected(self.rewordEnabled),
|
||||
),
|
||||
Description: self.c.Tr.RenameCommitEditor,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Remove),
|
||||
Handler: self.withItem(self.drop),
|
||||
Handler: self.withItemsRange(self.drop),
|
||||
GetDisabledReason: self.require(
|
||||
self.singleItemSelected(self.rebaseCommandEnabled(todo.Drop)),
|
||||
self.itemRangeSelected(
|
||||
self.midRebaseCommandEnabled,
|
||||
),
|
||||
),
|
||||
Description: self.c.Tr.DeleteCommit,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(editCommitKey),
|
||||
Handler: self.withItem(self.edit),
|
||||
Handler: self.withItems(self.edit),
|
||||
// TODO: have disabled reason ensure that if we're not rebasing, we only select one commit
|
||||
GetDisabledReason: self.require(
|
||||
self.singleItemSelected(self.rebaseCommandEnabled(todo.Edit)),
|
||||
self.itemRangeSelected(self.midRebaseCommandEnabled),
|
||||
),
|
||||
Description: self.c.Tr.EditCommit,
|
||||
},
|
||||
@ -107,7 +117,7 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
// when you manually select the base commit.
|
||||
Key: opts.GetKey(opts.Config.Commits.StartInteractiveRebase),
|
||||
Handler: self.withItem(self.quickStartInteractiveRebase),
|
||||
GetDisabledReason: self.require(self.notMidRebase, self.canFindCommitForQuickStart),
|
||||
GetDisabledReason: self.require(self.notMidRebase(self.c.Tr.AlreadyRebasing), self.canFindCommitForQuickStart),
|
||||
Description: self.c.Tr.QuickStartInteractiveRebase,
|
||||
Tooltip: utils.ResolvePlaceholderString(self.c.Tr.QuickStartInteractiveRebaseTooltip, map[string]string{
|
||||
"editKey": keybindings.Label(editCommitKey),
|
||||
@ -115,9 +125,9 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Commits.PickCommit),
|
||||
Handler: self.withItem(self.pick),
|
||||
Handler: self.withItems(self.pick),
|
||||
GetDisabledReason: self.require(
|
||||
self.singleItemSelected(self.rebaseCommandEnabled(todo.Pick)),
|
||||
self.itemRangeSelected(self.pickEnabled),
|
||||
),
|
||||
Description: self.c.Tr.PickCommit,
|
||||
},
|
||||
@ -131,22 +141,28 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
Key: opts.GetKey(opts.Config.Commits.SquashAboveCommits),
|
||||
Handler: self.withItem(self.squashAllAboveFixupCommits),
|
||||
GetDisabledReason: self.require(
|
||||
self.notMidRebase,
|
||||
self.notMidRebase(self.c.Tr.AlreadyRebasing),
|
||||
self.singleItemSelected(),
|
||||
),
|
||||
Description: self.c.Tr.SquashAboveCommits,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Commits.MoveDownCommit),
|
||||
Handler: self.withItem(self.moveDown),
|
||||
GetDisabledReason: self.require(self.singleItemSelected()),
|
||||
Description: self.c.Tr.MoveDownCommit,
|
||||
Key: opts.GetKey(opts.Config.Commits.MoveDownCommit),
|
||||
Handler: self.withItemsRange(self.moveDown),
|
||||
GetDisabledReason: self.require(self.itemRangeSelected(
|
||||
self.midRebaseCommandEnabled,
|
||||
self.canMoveDown,
|
||||
)),
|
||||
Description: self.c.Tr.MoveDownCommit,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Commits.MoveUpCommit),
|
||||
Handler: self.withItem(self.moveUp),
|
||||
GetDisabledReason: self.require(self.singleItemSelected()),
|
||||
Description: self.c.Tr.MoveUpCommit,
|
||||
Key: opts.GetKey(opts.Config.Commits.MoveUpCommit),
|
||||
Handler: self.withItemsRange(self.moveUp),
|
||||
GetDisabledReason: self.require(self.itemRangeSelected(
|
||||
self.midRebaseCommandEnabled,
|
||||
self.canMoveUp,
|
||||
)),
|
||||
Description: self.c.Tr.MoveUpCommit,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Commits.PasteCommits),
|
||||
@ -263,13 +279,9 @@ func secondaryPatchPanelUpdateOpts(c *ControllerCommon) *types.ViewUpdateOpts {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) squashDown(commit *models.Commit) error {
|
||||
applied, err := self.handleMidRebaseCommand(todo.Squash, commit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if applied {
|
||||
return nil
|
||||
func (self *LocalCommitsController) squashDown(selectedCommits []*models.Commit, startIdx int, endIdx int) error {
|
||||
if self.isRebasing() {
|
||||
return self.updateTodos(todo.Squash, selectedCommits)
|
||||
}
|
||||
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
@ -278,27 +290,15 @@ func (self *LocalCommitsController) squashDown(commit *models.Commit) error {
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.SquashCommitDown)
|
||||
return self.interactiveRebase(todo.Squash)
|
||||
return self.interactiveRebase(todo.Squash, startIdx, endIdx)
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) getDisabledReasonForSquashDown(commit *models.Commit) *types.DisabledReason {
|
||||
if self.context().GetSelectedLineIdx() >= len(self.c.Model().Commits)-1 {
|
||||
return &types.DisabledReason{Text: self.c.Tr.CannotSquashOrFixupFirstCommit}
|
||||
}
|
||||
|
||||
return self.rebaseCommandEnabled(todo.Squash)(commit)
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) fixup(commit *models.Commit) error {
|
||||
applied, err := self.handleMidRebaseCommand(todo.Fixup, commit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if applied {
|
||||
return nil
|
||||
func (self *LocalCommitsController) fixup(selectedCommits []*models.Commit, startIdx int, endIdx int) error {
|
||||
if self.isRebasing() {
|
||||
return self.updateTodos(todo.Fixup, selectedCommits)
|
||||
}
|
||||
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
@ -307,29 +307,13 @@ func (self *LocalCommitsController) fixup(commit *models.Commit) error {
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.FixupCommit)
|
||||
return self.interactiveRebase(todo.Fixup)
|
||||
return self.interactiveRebase(todo.Fixup, startIdx, endIdx)
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) getDisabledReasonForFixup(commit *models.Commit) *types.DisabledReason {
|
||||
if self.context().GetSelectedLineIdx() >= len(self.c.Model().Commits)-1 {
|
||||
return &types.DisabledReason{Text: self.c.Tr.CannotSquashOrFixupFirstCommit}
|
||||
}
|
||||
|
||||
return self.rebaseCommandEnabled(todo.Squash)(commit)
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) reword(commit *models.Commit) error {
|
||||
applied, err := self.handleMidRebaseCommand(todo.Reword, commit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if applied {
|
||||
return nil
|
||||
}
|
||||
|
||||
commitMessage, err := self.c.Git().Commit.GetCommitMessage(commit.Sha)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
@ -404,14 +388,6 @@ func (self *LocalCommitsController) doRewordEditor() error {
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) rewordEditor(commit *models.Commit) error {
|
||||
midRebase, err := self.handleMidRebaseCommand(todo.Reword, commit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if midRebase {
|
||||
return nil
|
||||
}
|
||||
|
||||
if self.c.UserConfig.Gui.SkipRewordInEditorWarning {
|
||||
return self.doRewordEditor()
|
||||
} else {
|
||||
@ -423,37 +399,37 @@ func (self *LocalCommitsController) rewordEditor(commit *models.Commit) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) drop(commit *models.Commit) error {
|
||||
applied, err := self.handleMidRebaseCommand(todo.Drop, commit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if applied {
|
||||
return nil
|
||||
func (self *LocalCommitsController) drop(selectedCommits []*models.Commit, startIdx int, endIdx int) error {
|
||||
if self.isRebasing() {
|
||||
return self.updateTodos(todo.Drop, selectedCommits)
|
||||
}
|
||||
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.DeleteCommitTitle,
|
||||
Prompt: self.c.Tr.DeleteCommitPrompt,
|
||||
Title: self.c.Tr.DropCommitTitle,
|
||||
Prompt: self.c.Tr.DropCommitPrompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(gocui.Task) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DroppingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DropCommit)
|
||||
return self.interactiveRebase(todo.Drop)
|
||||
return self.interactiveRebase(todo.Drop, startIdx, endIdx)
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) edit(commit *models.Commit) error {
|
||||
applied, err := self.handleMidRebaseCommand(todo.Edit, commit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if applied {
|
||||
return nil
|
||||
func (self *LocalCommitsController) edit(selectedCommits []*models.Commit) error {
|
||||
if self.isRebasing() {
|
||||
return self.updateTodos(todo.Edit, selectedCommits)
|
||||
}
|
||||
|
||||
return self.startInteractiveRebaseWithEdit(commit, commit)
|
||||
// TODO: support range select here (start a rebase and set the selected commits
|
||||
// to 'edit' in the todo file)
|
||||
if len(selectedCommits) > 1 {
|
||||
return self.c.ErrorMsg(self.c.Tr.RangeSelectNotSupported)
|
||||
}
|
||||
|
||||
selectedCommit := selectedCommits[0]
|
||||
|
||||
return self.startInteractiveRebaseWithEdit(selectedCommit, selectedCommit)
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) quickStartInteractiveRebase(selectedCommit *models.Commit) error {
|
||||
@ -504,13 +480,9 @@ func (self *LocalCommitsController) findCommitForQuickStartInteractiveRebase() (
|
||||
return commit, nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) pick(commit *models.Commit) error {
|
||||
applied, err := self.handleMidRebaseCommand(todo.Pick, commit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if applied {
|
||||
return nil
|
||||
func (self *LocalCommitsController) pick(selectedCommits []*models.Commit) error {
|
||||
if self.isRebasing() {
|
||||
return self.updateTodos(todo.Pick, selectedCommits)
|
||||
}
|
||||
|
||||
// at this point we aren't actually rebasing so we will interpret this as an
|
||||
@ -518,159 +490,93 @@ func (self *LocalCommitsController) pick(commit *models.Commit) error {
|
||||
return self.pullFiles()
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) interactiveRebase(action todo.TodoCommand) error {
|
||||
err := self.c.Git().Rebase.InteractiveRebase(self.c.Model().Commits, self.context().GetSelectedLineIdx(), action)
|
||||
func (self *LocalCommitsController) interactiveRebase(action todo.TodoCommand, startIdx int, endIdx int) error {
|
||||
// When performing an action that will remove the selected commits, we need to select the
|
||||
// next commit down (which will end up at the start index after the action is performed)
|
||||
if action == todo.Drop || action == todo.Fixup || action == todo.Squash {
|
||||
self.context().SetSelection(startIdx)
|
||||
}
|
||||
|
||||
err := self.c.Git().Rebase.InteractiveRebase(self.c.Model().Commits, startIdx, endIdx, action)
|
||||
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
}
|
||||
|
||||
// handleMidRebaseCommand sees if the selected commit is in fact a rebasing
|
||||
// updateTodos sees if the selected commit is in fact a rebasing
|
||||
// commit meaning you are trying to edit the todo file rather than actually
|
||||
// begin a rebase. It then updates the todo file with that action
|
||||
func (self *LocalCommitsController) handleMidRebaseCommand(action todo.TodoCommand, commit *models.Commit) (bool, error) {
|
||||
if !commit.IsTODO() {
|
||||
return false, nil
|
||||
func (self *LocalCommitsController) updateTodos(action todo.TodoCommand, selectedCommits []*models.Commit) error {
|
||||
if err := self.c.Git().Rebase.EditRebaseTodo(selectedCommits, action); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.LogAction("Update rebase TODO")
|
||||
|
||||
msg := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.Log.HandleMidRebaseCommand,
|
||||
map[string]string{
|
||||
"shortSha": commit.ShortSha(),
|
||||
"action": action.String(),
|
||||
},
|
||||
)
|
||||
self.c.LogCommand(msg, false)
|
||||
|
||||
if err := self.c.Git().Rebase.EditRebaseTodo(commit, action); err != nil {
|
||||
return false, self.c.Error(err)
|
||||
}
|
||||
|
||||
return true, self.c.Refresh(types.RefreshOptions{
|
||||
return self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) rebaseCommandEnabled(action todo.TodoCommand) func(*models.Commit) *types.DisabledReason {
|
||||
return func(commit *models.Commit) *types.DisabledReason {
|
||||
if commit.Action == models.ActionConflict {
|
||||
return &types.DisabledReason{Text: self.c.Tr.ChangingThisActionIsNotAllowed}
|
||||
}
|
||||
|
||||
if !commit.IsTODO() {
|
||||
if self.c.Model().WorkingTreeStateAtLastCommitRefresh != enums.REBASE_MODE_NONE {
|
||||
// If we are in a rebase, the only action that is allowed for
|
||||
// non-todo commits is rewording the current head commit
|
||||
if !(action == todo.Reword && self.isHeadCommit()) {
|
||||
return &types.DisabledReason{Text: self.c.Tr.AlreadyRebasing}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// for now we do not support setting 'reword' because it requires an editor
|
||||
// and that means we either unconditionally wait around for the subprocess to ask for
|
||||
// our input or we set a lazygit client as the EDITOR env variable and have it
|
||||
// request us to edit the commit message when prompted.
|
||||
if action == todo.Reword {
|
||||
return &types.DisabledReason{Text: self.c.Tr.RewordNotSupported}
|
||||
}
|
||||
|
||||
if allowed := isChangeOfRebaseTodoAllowed(action); !allowed {
|
||||
return &types.DisabledReason{Text: self.c.Tr.ChangingThisActionIsNotAllowed}
|
||||
}
|
||||
|
||||
return nil
|
||||
func (self *LocalCommitsController) rewordEnabled(commit *models.Commit) *types.DisabledReason {
|
||||
// for now we do not support setting 'reword' on TODO commits because it requires an editor
|
||||
// and that means we either unconditionally wait around for the subprocess to ask for
|
||||
// our input or we set a lazygit client as the EDITOR env variable and have it
|
||||
// request us to edit the commit message when prompted.
|
||||
if commit.IsTODO() {
|
||||
return &types.DisabledReason{Text: self.c.Tr.RewordNotSupported}
|
||||
}
|
||||
|
||||
// If we are in a rebase, the only action that is allowed for
|
||||
// non-todo commits is rewording the current head commit
|
||||
if self.isRebasing() && !self.isHeadCommit() {
|
||||
return &types.DisabledReason{Text: self.c.Tr.AlreadyRebasing}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) moveDown(commit *models.Commit) error {
|
||||
index := self.context().GetSelectedLineIdx()
|
||||
commits := self.c.Model().Commits
|
||||
func (self *LocalCommitsController) isRebasing() bool {
|
||||
return self.c.Model().WorkingTreeStateAtLastCommitRefresh != enums.REBASE_MODE_NONE
|
||||
}
|
||||
|
||||
// can't move past the initial commit
|
||||
if index >= len(commits)-1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if commit.IsTODO() {
|
||||
if !commits[index+1].IsTODO() || commits[index+1].Action == models.ActionConflict {
|
||||
return nil
|
||||
}
|
||||
|
||||
// logging directly here because MoveTodoDown doesn't have enough information
|
||||
// to provide a useful log
|
||||
self.c.LogAction(self.c.Tr.Actions.MoveCommitDown)
|
||||
|
||||
msg := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.Log.MovingCommitDown,
|
||||
map[string]string{
|
||||
"shortSha": commit.ShortSha(),
|
||||
},
|
||||
)
|
||||
self.c.LogCommand(msg, false)
|
||||
|
||||
if err := self.c.Git().Rebase.MoveTodoDown(commit); err != nil {
|
||||
func (self *LocalCommitsController) moveDown(selectedCommits []*models.Commit, startIdx int, endIdx int) error {
|
||||
if self.isRebasing() {
|
||||
if err := self.c.Git().Rebase.MoveTodosDown(selectedCommits); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
self.context().MoveSelectedLine(1)
|
||||
self.context().MoveSelection(1)
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS},
|
||||
})
|
||||
}
|
||||
|
||||
if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
||||
return self.c.ErrorMsg(self.c.Tr.AlreadyRebasing)
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatusSync(self.c.Tr.MovingStatus, func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.MoveCommitDown)
|
||||
err := self.c.Git().Rebase.MoveCommitDown(self.c.Model().Commits, index)
|
||||
err := self.c.Git().Rebase.MoveCommitsDown(self.c.Model().Commits, startIdx, endIdx)
|
||||
if err == nil {
|
||||
self.context().MoveSelectedLine(1)
|
||||
self.context().MoveSelection(1)
|
||||
}
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
|
||||
err, types.RefreshOptions{Mode: types.SYNC})
|
||||
})
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) moveUp(commit *models.Commit) error {
|
||||
index := self.context().GetSelectedLineIdx()
|
||||
if index == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if commit.IsTODO() {
|
||||
// logging directly here because MoveTodoDown doesn't have enough information
|
||||
// to provide a useful log
|
||||
self.c.LogAction(self.c.Tr.Actions.MoveCommitUp)
|
||||
msg := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.Log.MovingCommitUp,
|
||||
map[string]string{
|
||||
"shortSha": commit.ShortSha(),
|
||||
},
|
||||
)
|
||||
self.c.LogCommand(msg, false)
|
||||
|
||||
if err := self.c.Git().Rebase.MoveTodoUp(self.c.Model().Commits[index]); err != nil {
|
||||
func (self *LocalCommitsController) moveUp(selectedCommits []*models.Commit, startIdx int, endIdx int) error {
|
||||
if self.isRebasing() {
|
||||
if err := self.c.Git().Rebase.MoveTodosUp(selectedCommits); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
self.context().MoveSelectedLine(-1)
|
||||
self.context().MoveSelection(-1)
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS},
|
||||
})
|
||||
}
|
||||
|
||||
if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
||||
return self.c.ErrorMsg(self.c.Tr.AlreadyRebasing)
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatusSync(self.c.Tr.MovingStatus, func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.MoveCommitUp)
|
||||
err := self.c.Git().Rebase.MoveCommitUp(self.c.Model().Commits, index)
|
||||
err := self.c.Git().Rebase.MoveCommitsUp(self.c.Model().Commits, startIdx, endIdx)
|
||||
if err == nil {
|
||||
self.context().MoveSelectedLine(-1)
|
||||
self.context().MoveSelection(-1)
|
||||
}
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
|
||||
err, types.RefreshOptions{Mode: types.SYNC})
|
||||
@ -693,10 +599,6 @@ func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
|
||||
})
|
||||
}
|
||||
|
||||
if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
||||
return self.c.ErrorMsg(self.c.Tr.AlreadyRebasing)
|
||||
}
|
||||
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.AmendCommitTitle,
|
||||
Prompt: self.c.Tr.AmendCommitPrompt,
|
||||
@ -713,7 +615,7 @@ func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) canAmend(commit *models.Commit) *types.DisabledReason {
|
||||
if !self.isHeadCommit() && self.c.Model().WorkingTreeStateAtLastCommitRefresh != enums.REBASE_MODE_NONE {
|
||||
if !self.isHeadCommit() && self.isRebasing() {
|
||||
return &types.DisabledReason{Text: self.c.Tr.AlreadyRebasing}
|
||||
}
|
||||
|
||||
@ -721,10 +623,6 @@ func (self *LocalCommitsController) canAmend(commit *models.Commit) *types.Disab
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) amendAttribute(commit *models.Commit) error {
|
||||
if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE && !self.isHeadCommit() {
|
||||
return self.c.ErrorMsg(self.c.Tr.AlreadyRebasing)
|
||||
}
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: "Amend commit attribute",
|
||||
Items: []*types.MenuItem{
|
||||
@ -846,7 +744,7 @@ func (self *LocalCommitsController) createRevertMergeCommitMenu(commit *models.C
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) afterRevertCommit() error {
|
||||
self.context().MoveSelectedLine(1)
|
||||
self.context().MoveSelection(1)
|
||||
return self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS, types.BRANCHES},
|
||||
})
|
||||
@ -895,24 +793,6 @@ func (self *LocalCommitsController) squashAllAboveFixupCommits(commit *models.Co
|
||||
})
|
||||
}
|
||||
|
||||
// For getting disabled reason
|
||||
func (self *LocalCommitsController) notMidRebase() *types.DisabledReason {
|
||||
if self.c.Model().WorkingTreeStateAtLastCommitRefresh != enums.REBASE_MODE_NONE {
|
||||
return &types.DisabledReason{Text: self.c.Tr.AlreadyRebasing}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// For getting disabled reason
|
||||
func (self *LocalCommitsController) canFindCommitForQuickStart() *types.DisabledReason {
|
||||
if _, err := self.findCommitForQuickStartInteractiveRebase(); err != nil {
|
||||
return &types.DisabledReason{Text: err.Error(), ShowErrorInPanel: true}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) createTag(commit *models.Commit) error {
|
||||
return self.c.Helpers().Tags.OpenCreateTagPrompt(commit.Sha, func() {})
|
||||
}
|
||||
@ -1079,15 +959,106 @@ func (self *LocalCommitsController) isHeadCommit() bool {
|
||||
return models.IsHeadCommit(self.c.Model().Commits, self.context().GetSelectedLineIdx())
|
||||
}
|
||||
|
||||
func isChangeOfRebaseTodoAllowed(action todo.TodoCommand) bool {
|
||||
allowedActions := []todo.TodoCommand{
|
||||
todo.Pick,
|
||||
todo.Drop,
|
||||
todo.Edit,
|
||||
todo.Fixup,
|
||||
todo.Squash,
|
||||
todo.Reword,
|
||||
func (self *LocalCommitsController) notMidRebase(message string) func() *types.DisabledReason {
|
||||
return func() *types.DisabledReason {
|
||||
if self.isRebasing() {
|
||||
return &types.DisabledReason{Text: message}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) canFindCommitForQuickStart() *types.DisabledReason {
|
||||
if _, err := self.findCommitForQuickStartInteractiveRebase(); err != nil {
|
||||
return &types.DisabledReason{Text: err.Error(), ShowErrorInPanel: true}
|
||||
}
|
||||
|
||||
return lo.Contains(allowedActions, action)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) canSquashOrFixup(_selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason {
|
||||
if endIdx >= len(self.c.Model().Commits)-1 {
|
||||
return &types.DisabledReason{Text: self.c.Tr.CannotSquashOrFixupFirstCommit}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) canMoveDown(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason {
|
||||
if endIdx >= len(self.c.Model().Commits)-1 {
|
||||
return &types.DisabledReason{Text: self.c.Tr.CannotMoveAnyFurther}
|
||||
}
|
||||
|
||||
if self.isRebasing() {
|
||||
commits := self.c.Model().Commits
|
||||
|
||||
if !commits[endIdx+1].IsTODO() || commits[endIdx+1].Action == models.ActionConflict {
|
||||
return &types.DisabledReason{Text: self.c.Tr.CannotMoveAnyFurther}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) canMoveUp(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason {
|
||||
if startIdx == 0 {
|
||||
return &types.DisabledReason{Text: self.c.Tr.CannotMoveAnyFurther}
|
||||
}
|
||||
|
||||
if self.isRebasing() {
|
||||
commits := self.c.Model().Commits
|
||||
|
||||
if !commits[startIdx-1].IsTODO() || commits[startIdx-1].Action == models.ActionConflict {
|
||||
return &types.DisabledReason{Text: self.c.Tr.CannotMoveAnyFurther}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensures that if we are mid-rebase, we're only selecting valid commits (non-conflict TODO commits)
|
||||
func (self *LocalCommitsController) midRebaseCommandEnabled(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason {
|
||||
if !self.isRebasing() {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, commit := range selectedCommits {
|
||||
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{
|
||||
todo.Pick,
|
||||
todo.Drop,
|
||||
todo.Edit,
|
||||
todo.Fixup,
|
||||
todo.Squash,
|
||||
todo.Reword,
|
||||
}
|
||||
|
||||
func isChangeOfRebaseTodoAllowed(oldAction todo.TodoCommand) bool {
|
||||
// Only allow updating a standard action, meaning we disallow
|
||||
// updating a merge commit or update ref commit (until we decide what would be sensible
|
||||
// to do in those cases)
|
||||
return lo.Contains(standardActions, oldAction)
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) pickEnabled(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason {
|
||||
if !self.isRebasing() {
|
||||
// if not rebasing, we're going to do a pull so we don't care about the selection
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.midRebaseCommandEnabled(selectedCommits, startIdx, endIdx)
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ func NewMenuController(
|
||||
c,
|
||||
c.Contexts().Menu,
|
||||
c.Contexts().Menu.GetSelected,
|
||||
c.Contexts().Menu.GetSelectedItems,
|
||||
),
|
||||
c: c,
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ func NewReflogCommitsController(
|
||||
c,
|
||||
c.Contexts().ReflogCommits,
|
||||
c.Contexts().ReflogCommits.GetSelected,
|
||||
c.Contexts().ReflogCommits.GetSelectedItems,
|
||||
),
|
||||
c: c,
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ func NewRemoteBranchesController(
|
||||
c,
|
||||
c.Contexts().RemoteBranches,
|
||||
c.Contexts().RemoteBranches.GetSelected,
|
||||
c.Contexts().RemoteBranches.GetSelectedItems,
|
||||
),
|
||||
c: c,
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ func NewRemotesController(
|
||||
c,
|
||||
c.Contexts().Remotes,
|
||||
c.Contexts().Remotes.GetSelected,
|
||||
c.Contexts().Remotes.GetSelectedItems,
|
||||
),
|
||||
c: c,
|
||||
setRemoteBranches: setRemoteBranches,
|
||||
|
@ -24,6 +24,7 @@ func NewStashController(
|
||||
c,
|
||||
c.Contexts().Stash,
|
||||
c.Contexts().Stash.GetSelected,
|
||||
c.Contexts().Stash.GetSelectedItems,
|
||||
),
|
||||
c: c,
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ func NewSubCommitsController(
|
||||
c,
|
||||
c.Contexts().SubCommits,
|
||||
c.Contexts().SubCommits.GetSelected,
|
||||
c.Contexts().SubCommits.GetSelectedItems,
|
||||
),
|
||||
c: c,
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ func NewSubmodulesController(
|
||||
c,
|
||||
c.Contexts().Submodules,
|
||||
c.Contexts().Submodules.GetSelected,
|
||||
c.Contexts().Submodules.GetSelectedItems,
|
||||
),
|
||||
c: c,
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ func NewSuggestionsController(
|
||||
c,
|
||||
c.Contexts().Suggestions,
|
||||
c.Contexts().Suggestions.GetSelected,
|
||||
c.Contexts().Suggestions.GetSelectedItems,
|
||||
),
|
||||
c: c,
|
||||
}
|
||||
|
@ -36,6 +36,9 @@ func NewSwitchToDiffFilesController(
|
||||
c,
|
||||
context,
|
||||
context.GetSelectedRef,
|
||||
func() ([]types.Ref, int, int) {
|
||||
panic("Not implemented")
|
||||
},
|
||||
),
|
||||
c: c,
|
||||
context: context,
|
||||
|
@ -32,6 +32,9 @@ func NewSwitchToSubCommitsController(
|
||||
c,
|
||||
context,
|
||||
context.GetSelectedRef,
|
||||
func() ([]types.Ref, int, int) {
|
||||
panic("Not implemented")
|
||||
},
|
||||
),
|
||||
c: c,
|
||||
context: context,
|
||||
|
@ -25,6 +25,7 @@ func NewTagsController(
|
||||
c,
|
||||
c.Contexts().Tags,
|
||||
c.Contexts().Tags.GetSelected,
|
||||
c.Contexts().Tags.GetSelectedItems,
|
||||
),
|
||||
c: c,
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ func NewWorktreeOptionsController(c *ControllerCommon, context CanViewWorktreeOp
|
||||
c,
|
||||
context,
|
||||
context.GetSelectedItemId,
|
||||
context.GetSelectedItemIds,
|
||||
),
|
||||
c: c,
|
||||
context: context,
|
||||
|
@ -28,6 +28,7 @@ func NewWorktreesController(
|
||||
c,
|
||||
c.Contexts().Worktrees,
|
||||
c.Contexts().Worktrees.GetSelected,
|
||||
c.Contexts().Worktrees.GetSelectedItems,
|
||||
),
|
||||
c: c,
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/samber/lo"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -69,6 +70,29 @@ func (self *CommitFileTreeViewModel) GetSelected() *CommitFileNode {
|
||||
return self.Get(self.GetSelectedLineIdx())
|
||||
}
|
||||
|
||||
func (self *CommitFileTreeViewModel) GetSelectedItemId() string {
|
||||
item := self.GetSelected()
|
||||
if item == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
||||
func (self *CommitFileTreeViewModel) GetSelectedItems() ([]*CommitFileNode, int, int) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
func (self *CommitFileTreeViewModel) GetSelectedItemIds() ([]string, int, int) {
|
||||
selectedItems, startIdx, endIdx := self.GetSelectedItems()
|
||||
|
||||
ids := lo.Map(selectedItems, func(item *CommitFileNode, _ int) string {
|
||||
return item.ID()
|
||||
})
|
||||
|
||||
return ids, startIdx, endIdx
|
||||
}
|
||||
|
||||
func (self *CommitFileTreeViewModel) GetSelectedFile() *models.CommitFile {
|
||||
node := self.GetSelected()
|
||||
if node == nil {
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -43,6 +44,36 @@ func (self *FileTreeViewModel) GetSelected() *FileNode {
|
||||
return self.Get(self.GetSelectedLineIdx())
|
||||
}
|
||||
|
||||
func (self *FileTreeViewModel) GetSelectedItemId() string {
|
||||
item := self.GetSelected()
|
||||
if item == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
||||
func (self *FileTreeViewModel) GetSelectedItems() ([]*FileNode, int, int) {
|
||||
startIdx, endIdx := self.GetSelectionRange()
|
||||
|
||||
nodes := []*FileNode{}
|
||||
for i := startIdx; i <= endIdx; i++ {
|
||||
nodes = append(nodes, self.Get(i))
|
||||
}
|
||||
|
||||
return nodes, startIdx, endIdx
|
||||
}
|
||||
|
||||
func (self *FileTreeViewModel) GetSelectedItemIds() ([]string, int, int) {
|
||||
selectedItems, startIdx, endIdx := self.GetSelectedItems()
|
||||
|
||||
ids := lo.Map(selectedItems, func(item *FileNode, _ int) string {
|
||||
return item.ID()
|
||||
})
|
||||
|
||||
return ids, startIdx, endIdx
|
||||
}
|
||||
|
||||
func (self *FileTreeViewModel) GetSelectedFile() *models.File {
|
||||
node := self.GetSelected()
|
||||
if node == nil {
|
||||
|
@ -242,6 +242,12 @@ type MenuItem struct {
|
||||
Section *MenuSection
|
||||
}
|
||||
|
||||
// Defining this for the sake of conforming to the HasID interface, which is used
|
||||
// in list contexts.
|
||||
func (self *MenuItem) ID() string {
|
||||
return self.Label
|
||||
}
|
||||
|
||||
type Model struct {
|
||||
CommitFiles []*models.CommitFile
|
||||
Files []*models.File
|
||||
|
@ -136,6 +136,7 @@ type IListContext interface {
|
||||
Context
|
||||
|
||||
GetSelectedItemId() string
|
||||
GetSelectedItemIds() ([]string, int, int)
|
||||
IsItemVisible(item HasUrn) bool
|
||||
|
||||
GetList() IList
|
||||
|
@ -6,3 +6,8 @@ type Suggestion struct {
|
||||
// label is what is actually displayed so it can e.g. contain color
|
||||
Label string
|
||||
}
|
||||
|
||||
// Conforming to the HasID interface, which is needed for list contexts
|
||||
func (self *Suggestion) ID() string {
|
||||
return self.Value
|
||||
}
|
||||
|
@ -217,8 +217,8 @@ func chineseTranslationSet() TranslationSet {
|
||||
ScrollDownMainPanel: "向下滚动主面板",
|
||||
AmendCommitTitle: "修改提交",
|
||||
AmendCommitPrompt: "您确定要使用暂存文件来修改此提交吗?",
|
||||
DeleteCommitTitle: "删除提交",
|
||||
DeleteCommitPrompt: "您确定要删除此提交吗?",
|
||||
DropCommitTitle: "删除提交",
|
||||
DropCommitPrompt: "您确定要删除此提交吗?",
|
||||
PullingStatus: "正在拉取",
|
||||
PushingStatus: "正在推送",
|
||||
FetchingStatus: "正在抓取",
|
||||
|
@ -181,8 +181,8 @@ func dutchTranslationSet() TranslationSet {
|
||||
ScrollDownMainPanel: "Scroll naar beneden vanaf hoofdpaneel",
|
||||
AmendCommitTitle: "Commit wijzigen",
|
||||
AmendCommitPrompt: "Weet je zeker dat je deze commit wil wijzigen met de vorige staged bestanden?",
|
||||
DeleteCommitTitle: "Verwijder commit",
|
||||
DeleteCommitPrompt: "Weet je zeker dat je deze commit wil verwijderen?",
|
||||
DropCommitTitle: "Verwijder commit",
|
||||
DropCommitPrompt: "Weet je zeker dat je deze commit wil verwijderen?",
|
||||
PullingStatus: "Pullen",
|
||||
PushingStatus: "Pushen",
|
||||
FetchingStatus: "Fetchen",
|
||||
|
@ -119,6 +119,7 @@ type TranslationSet struct {
|
||||
DeleteCommit string
|
||||
MoveDownCommit string
|
||||
MoveUpCommit string
|
||||
CannotMoveAnyFurther string
|
||||
EditCommit string
|
||||
AmendToCommit string
|
||||
ResetAuthor string
|
||||
@ -239,6 +240,7 @@ type TranslationSet struct {
|
||||
SimpleRebase string
|
||||
InteractiveRebase string
|
||||
InteractiveRebaseTooltip string
|
||||
MustSelectTodoCommits string
|
||||
ConfirmMerge string
|
||||
FwdNoUpstream string
|
||||
FwdNoLocalUpstream string
|
||||
@ -270,14 +272,15 @@ type TranslationSet struct {
|
||||
ScrollDownMainPanel string
|
||||
AmendCommitTitle string
|
||||
AmendCommitPrompt string
|
||||
DeleteCommitTitle string
|
||||
DeleteCommitPrompt string
|
||||
DropCommitTitle string
|
||||
DropCommitPrompt string
|
||||
PullingStatus string
|
||||
PushingStatus string
|
||||
FetchingStatus string
|
||||
SquashingStatus string
|
||||
FixingStatus string
|
||||
DeletingStatus string
|
||||
DroppingStatus string
|
||||
MovingStatus string
|
||||
RebasingStatus string
|
||||
MergingStatus string
|
||||
@ -686,8 +689,6 @@ type Log struct {
|
||||
CherryPickCommits string
|
||||
HandleUndo string
|
||||
HandleMidRebaseCommand string
|
||||
MovingCommitUp string
|
||||
MovingCommitDown string
|
||||
RemoveFile string
|
||||
CopyToClipboard string
|
||||
Remove string
|
||||
@ -945,8 +946,8 @@ func EnglishTranslationSet() TranslationSet {
|
||||
UpdateRefHere: "Update branch '{{.ref}}' here",
|
||||
CannotSquashOrFixupFirstCommit: "There's no commit below to squash into",
|
||||
Fixup: "Fixup",
|
||||
SureFixupThisCommit: "Are you sure you want to 'fixup' this commit? It will be merged into the commit below",
|
||||
SureSquashThisCommit: "Are you sure you want to squash this commit into the commit below?",
|
||||
SureFixupThisCommit: "Are you sure you want to 'fixup' the selected commit(s) into the commit below?",
|
||||
SureSquashThisCommit: "Are you sure you want to squash the selected commit(s) into the commit below?",
|
||||
Squash: "Squash",
|
||||
PickCommit: "Pick commit (when mid-rebase)",
|
||||
RevertCommit: "Revert commit",
|
||||
@ -954,6 +955,7 @@ func EnglishTranslationSet() TranslationSet {
|
||||
DeleteCommit: "Delete commit",
|
||||
MoveDownCommit: "Move commit down one",
|
||||
MoveUpCommit: "Move commit up one",
|
||||
CannotMoveAnyFurther: "Cannot move any further",
|
||||
EditCommit: "Edit commit",
|
||||
AmendToCommit: "Amend commit with staged changes",
|
||||
ResetAuthor: "Reset author",
|
||||
@ -1079,6 +1081,7 @@ func EnglishTranslationSet() TranslationSet {
|
||||
SimpleRebase: "Simple rebase",
|
||||
InteractiveRebase: "Interactive rebase",
|
||||
InteractiveRebaseTooltip: "Begin an interactive rebase with a break at the start, so you can update the TODO commits before continuing",
|
||||
MustSelectTodoCommits: "When rebasing, this action only works on a selection of TODO commits.",
|
||||
ConfirmMerge: "Are you sure you want to merge '{{.selectedBranch}}' into '{{.checkedOutBranch}}'?",
|
||||
FwdNoUpstream: "Cannot fast-forward a branch with no upstream",
|
||||
FwdNoLocalUpstream: "Cannot fast-forward a branch whose remote is not registered locally",
|
||||
@ -1110,14 +1113,15 @@ func EnglishTranslationSet() TranslationSet {
|
||||
ScrollDownMainPanel: "Scroll down main panel",
|
||||
AmendCommitTitle: "Amend commit",
|
||||
AmendCommitPrompt: "Are you sure you want to amend this commit with your staged files?",
|
||||
DeleteCommitTitle: "Delete commit",
|
||||
DeleteCommitPrompt: "Are you sure you want to delete this commit?",
|
||||
DropCommitTitle: "Drop commit",
|
||||
DropCommitPrompt: "Are you sure you want to drop the selected commit(s)?",
|
||||
PullingStatus: "Pulling",
|
||||
PushingStatus: "Pushing",
|
||||
FetchingStatus: "Fetching",
|
||||
SquashingStatus: "Squashing",
|
||||
FixingStatus: "Fixing up",
|
||||
DeletingStatus: "Deleting",
|
||||
DroppingStatus: "Dropping",
|
||||
MovingStatus: "Moving",
|
||||
RebasingStatus: "Rebasing",
|
||||
MergingStatus: "Merging",
|
||||
@ -1626,8 +1630,6 @@ func EnglishTranslationSet() TranslationSet {
|
||||
CherryPickCommits: "Cherry-picking commits:\n'{{.commitLines}}'",
|
||||
HandleUndo: "Undoing last conflict resolution",
|
||||
HandleMidRebaseCommand: "Updating rebase action of commit {{.shortSha}} to '{{.action}}'",
|
||||
MovingCommitUp: "Moving commit {{.shortSha}} up",
|
||||
MovingCommitDown: "Moving commit {{.shortSha}} down",
|
||||
RemoveFile: "Deleting path '{{.path}}'",
|
||||
CopyToClipboard: "Copying '{{.str}}' to clipboard",
|
||||
Remove: "Removing '{{.filename}}'",
|
||||
|
@ -221,8 +221,8 @@ func japaneseTranslationSet() TranslationSet {
|
||||
ScrollDownMainPanel: "メインパネルを下にスクロール",
|
||||
AmendCommitTitle: "Amendコミット",
|
||||
AmendCommitPrompt: "ステージされたファイルで現在のコミットをamendします。よろしいですか?",
|
||||
DeleteCommitTitle: "コミットを削除",
|
||||
DeleteCommitPrompt: "選択されたコミットを削除します。よろしいですか?",
|
||||
DropCommitTitle: "コミットを削除",
|
||||
DropCommitPrompt: "選択されたコミットを削除します。よろしいですか?",
|
||||
PullingStatus: "Pull中",
|
||||
PushingStatus: "Push中",
|
||||
FetchingStatus: "Fetch中",
|
||||
|
@ -218,8 +218,8 @@ func koreanTranslationSet() TranslationSet {
|
||||
ScrollDownMainPanel: "메인 패널을 아래로로 스크롤",
|
||||
AmendCommitTitle: "Amend commit",
|
||||
AmendCommitPrompt: "Are you sure you want to amend this commit with your staged files?",
|
||||
DeleteCommitTitle: "커밋 삭제",
|
||||
DeleteCommitPrompt: "정말로 선택한 커밋을 삭제하시겠습니까?",
|
||||
DropCommitTitle: "커밋 삭제",
|
||||
DropCommitPrompt: "정말로 선택한 커밋을 삭제하시겠습니까?",
|
||||
PullingStatus: "업데이트 중",
|
||||
PushingStatus: "푸시 중",
|
||||
FetchingStatus: "패치 중",
|
||||
|
@ -147,8 +147,8 @@ func polishTranslationSet() TranslationSet {
|
||||
ScrollUp: "Przewiń w górę",
|
||||
AmendCommitTitle: "Popraw commit",
|
||||
AmendCommitPrompt: "Czy na pewno chcesz poprawić ten commit plikami z poczekalni?",
|
||||
DeleteCommitTitle: "Usuń commit",
|
||||
DeleteCommitPrompt: "Czy na pewno usunąć ten commit?",
|
||||
DropCommitTitle: "Usuń commit",
|
||||
DropCommitPrompt: "Czy na pewno usunąć ten commit?",
|
||||
PullingStatus: "Pobieranie zmian",
|
||||
PushingStatus: "Wysyłanie zmian",
|
||||
FetchingStatus: "Pobieram",
|
||||
|
@ -262,8 +262,8 @@ func RussianTranslationSet() TranslationSet {
|
||||
ScrollDownMainPanel: "Прокрутить вниз главную панель",
|
||||
AmendCommitTitle: "Править коммит (amend)",
|
||||
AmendCommitPrompt: "Вы уверены, что хотите править этот коммит проиндексированными файлами?",
|
||||
DeleteCommitTitle: "Удалить коммит",
|
||||
DeleteCommitPrompt: "Вы уверены, что хотите удалить этот коммит?",
|
||||
DropCommitTitle: "Удалить коммит",
|
||||
DropCommitPrompt: "Вы уверены, что хотите удалить этот коммит?",
|
||||
PullingStatus: "Получение и слияние изменении",
|
||||
PushingStatus: "Отправка изменении",
|
||||
FetchingStatus: "Получение изменении",
|
||||
|
@ -293,8 +293,8 @@ func traditionalChineseTranslationSet() TranslationSet {
|
||||
ScrollDownMainPanel: "向下捲動主面板",
|
||||
AmendCommitTitle: "修正提交",
|
||||
AmendCommitPrompt: "你確定要使用預存的檔案修正此提交嗎?",
|
||||
DeleteCommitTitle: "刪除提交",
|
||||
DeleteCommitPrompt: "你確定要刪除此提交嗎?",
|
||||
DropCommitTitle: "刪除提交",
|
||||
DropCommitPrompt: "你確定要刪除此提交嗎?",
|
||||
PullingStatus: "拉取",
|
||||
PushingStatus: "推送",
|
||||
FetchingStatus: "擷取",
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
)
|
||||
|
||||
type TextMatcher struct {
|
||||
// If you add or change a field here, be sure to update the copy
|
||||
// code in checkIsSelected()
|
||||
*Matcher[string]
|
||||
}
|
||||
|
||||
@ -95,8 +97,8 @@ func (self *TextMatcher) IsSelected() *TextMatcher {
|
||||
// if the matcher has an `IsSelected` rule, it returns true, along with the matcher after that rule has been removed
|
||||
func (self *TextMatcher) checkIsSelected() (bool, *TextMatcher) {
|
||||
// copying into a new matcher in case we want to re-use the original later
|
||||
newMatcher := &TextMatcher{}
|
||||
*newMatcher = *self
|
||||
newMatcher := &TextMatcher{Matcher: &Matcher[string]{}}
|
||||
*newMatcher.Matcher = *self.Matcher
|
||||
|
||||
check := lo.ContainsBy(newMatcher.rules, func(rule matcherRule[string]) bool { return rule.name == IS_SELECTED_RULE_NAME })
|
||||
|
||||
|
@ -211,29 +211,63 @@ func (self *ViewDriver) validateVisibleLineCount(matchers []*TextMatcher) {
|
||||
func (self *ViewDriver) assertLines(offset int, matchers ...*TextMatcher) *ViewDriver {
|
||||
view := self.getView()
|
||||
|
||||
var expectedStartIdx, expectedEndIdx int
|
||||
foundSelectionStart := false
|
||||
foundSelectionEnd := false
|
||||
expectedSelectedLines := []string{}
|
||||
|
||||
for matcherIndex, matcher := range matchers {
|
||||
lineIdx := matcherIndex + offset
|
||||
|
||||
checkIsSelected, matcher := matcher.checkIsSelected()
|
||||
|
||||
if checkIsSelected {
|
||||
if foundSelectionEnd {
|
||||
self.t.fail("The IsSelected matcher can only be used on a contiguous range of lines.")
|
||||
}
|
||||
if !foundSelectionStart {
|
||||
expectedStartIdx = lineIdx
|
||||
foundSelectionStart = true
|
||||
}
|
||||
expectedSelectedLines = append(expectedSelectedLines, matcher.name())
|
||||
expectedEndIdx = lineIdx
|
||||
} else if foundSelectionStart {
|
||||
foundSelectionEnd = true
|
||||
}
|
||||
}
|
||||
|
||||
for matcherIndex, matcher := range matchers {
|
||||
lineIdx := matcherIndex + offset
|
||||
expectSelected, matcher := matcher.checkIsSelected()
|
||||
|
||||
self.t.matchString(matcher, fmt.Sprintf("Unexpected content in view '%s'.", view.Name()),
|
||||
func() string {
|
||||
return view.BufferLines()[lineIdx]
|
||||
},
|
||||
)
|
||||
|
||||
if checkIsSelected {
|
||||
// If any of the matchers care about the selection, we need to
|
||||
// assert on the selection for each matcher.
|
||||
if foundSelectionStart {
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
startIdx, endIdx := self.getSelectedRange()
|
||||
|
||||
if lineIdx < startIdx || lineIdx > endIdx {
|
||||
if startIdx == endIdx {
|
||||
return false, fmt.Sprintf("Unexpected selected line index in view '%s'. Expected %d, got %d", view.Name(), lineIdx, startIdx)
|
||||
} else {
|
||||
lines := self.getSelectedLines()
|
||||
return false, fmt.Sprintf("Unexpected selected line index in view '%s'. Expected line %d to be in range %d to %d. Selected lines:\n---\n%s\n---\n\nExpected line: '%s'", view.Name(), lineIdx, startIdx, endIdx, strings.Join(lines, "\n"), matcher.name())
|
||||
}
|
||||
selected := lineIdx >= startIdx && lineIdx <= endIdx
|
||||
|
||||
if (selected && expectSelected) || (!selected && !expectSelected) {
|
||||
return true, ""
|
||||
}
|
||||
return true, ""
|
||||
|
||||
lines := self.getSelectedLines()
|
||||
|
||||
return false, fmt.Sprintf(
|
||||
"Unexpected selection in view '%s'. Expected %s to be selected but got %s.\nExpected selected lines:\n---\n%s\n---\n\nActual selected lines:\n---\n%s\n---\n",
|
||||
view.Name(),
|
||||
formatLineRange(startIdx, endIdx),
|
||||
formatLineRange(expectedStartIdx, expectedEndIdx),
|
||||
strings.Join(lines, "\n"),
|
||||
strings.Join(expectedSelectedLines, "\n"),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -241,6 +275,14 @@ func (self *ViewDriver) assertLines(offset int, matchers ...*TextMatcher) *ViewD
|
||||
return self
|
||||
}
|
||||
|
||||
func formatLineRange(from int, to int) string {
|
||||
if from == to {
|
||||
return "line " + fmt.Sprintf("%d", from)
|
||||
}
|
||||
|
||||
return "lines " + fmt.Sprintf("%d-%d", from, to)
|
||||
}
|
||||
|
||||
// asserts on the content of the view i.e. the stuff within the view's frame.
|
||||
func (self *ViewDriver) Content(matcher *TextMatcher) *ViewDriver {
|
||||
self.t.matchString(matcher, fmt.Sprintf("%s: Unexpected content.", self.context),
|
||||
|
@ -22,8 +22,8 @@ var Undo = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
|
||||
confirmCommitDrop := func() {
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Delete commit")).
|
||||
Content(Equals("Are you sure you want to delete this commit?")).
|
||||
Title(Equals("Drop commit")).
|
||||
Content(Equals("Are you sure you want to drop the selected commit(s)?")).
|
||||
Wait(500).
|
||||
Confirm()
|
||||
}
|
||||
|
@ -23,8 +23,8 @@ var DropWithCustomCommentChar = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Press(keys.Universal.Remove).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Delete commit")).
|
||||
Content(Equals("Are you sure you want to delete this commit?")).
|
||||
Title(Equals("Drop commit")).
|
||||
Content(Equals("Are you sure you want to drop the selected commit(s)?")).
|
||||
Confirm()
|
||||
}).
|
||||
Lines(
|
||||
|
@ -29,6 +29,6 @@ var EditNonTodoCommitDuringRebase = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
NavigateToLine(Contains("commit 01")).
|
||||
Press(keys.Universal.Edit)
|
||||
|
||||
t.ExpectToast(Contains("Can't perform this action during a rebase"))
|
||||
t.ExpectToast(Contains("Disabled: When rebasing, this action only works on a selection of TODO commits."))
|
||||
},
|
||||
})
|
||||
|
@ -39,6 +39,6 @@ var EditTheConflCommit = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
NavigateToLine(Contains("<-- YOU ARE HERE --- commit three")).
|
||||
Press(keys.Commits.RenameCommit)
|
||||
|
||||
t.ExpectToast(Contains("Changing this kind of rebase todo entry is not allowed"))
|
||||
t.ExpectToast(Contains("Disabled: Rewording commits while interactively rebasing is not currently supported"))
|
||||
},
|
||||
})
|
||||
|
@ -29,7 +29,7 @@ var FixupSecondCommit = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Fixup")).
|
||||
Content(Equals("Are you sure you want to 'fixup' this commit? It will be merged into the commit below")).
|
||||
Content(Equals("Are you sure you want to 'fixup' the selected commit(s) into the commit below?")).
|
||||
Confirm()
|
||||
}).
|
||||
Lines(
|
||||
|
@ -0,0 +1,205 @@
|
||||
package interactive_rebase
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var MidRebaseRangeSelect = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Do various things with range selection in the commits view when mid-rebase",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.
|
||||
CreateNCommits(10)
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
TopLines(
|
||||
Contains("commit 10").IsSelected(),
|
||||
).
|
||||
NavigateToLine(Contains("commit 07")).
|
||||
Press(keys.Universal.RangeSelectDown).
|
||||
TopLines(
|
||||
Contains("commit 10"),
|
||||
Contains("commit 09"),
|
||||
Contains("commit 08"),
|
||||
Contains("commit 07").IsSelected(),
|
||||
Contains("commit 06").IsSelected(),
|
||||
Contains("commit 05"),
|
||||
Contains("commit 04"),
|
||||
).
|
||||
// Verify we can't perform an edit on multiple commits (it's not supported
|
||||
// yet)
|
||||
Press(keys.Universal.Edit).
|
||||
Tap(func() {
|
||||
// This ought to be a toast but I'm too lazy to implement that right now.
|
||||
t.ExpectPopup().Alert().
|
||||
Title(Equals("Error")).
|
||||
Content(Contains("Action does not support range selection, please select a single item")).
|
||||
Confirm()
|
||||
}).
|
||||
NavigateToLine(Contains("commit 05")).
|
||||
// Start a rebase
|
||||
Press(keys.Universal.Edit).
|
||||
TopLines(
|
||||
Contains("pick").Contains("commit 10"),
|
||||
Contains("pick").Contains("commit 09"),
|
||||
Contains("pick").Contains("commit 08"),
|
||||
Contains("pick").Contains("commit 07"),
|
||||
Contains("pick").Contains("commit 06"),
|
||||
Contains("<-- YOU ARE HERE --- commit 05").IsSelected(),
|
||||
Contains("commit 04"),
|
||||
).
|
||||
SelectPreviousItem().
|
||||
// perform various actions on a range of commits
|
||||
Press(keys.Universal.RangeSelectUp).
|
||||
TopLines(
|
||||
Contains("pick").Contains("commit 10"),
|
||||
Contains("pick").Contains("commit 09"),
|
||||
Contains("pick").Contains("commit 08"),
|
||||
Contains("pick").Contains("commit 07").IsSelected(),
|
||||
Contains("pick").Contains("commit 06").IsSelected(),
|
||||
Contains("<-- YOU ARE HERE --- commit 05"),
|
||||
Contains("commit 04"),
|
||||
).
|
||||
Press(keys.Commits.MarkCommitAsFixup).
|
||||
TopLines(
|
||||
Contains("pick").Contains("commit 10"),
|
||||
Contains("pick").Contains("commit 09"),
|
||||
Contains("pick").Contains("commit 08"),
|
||||
Contains("fixup").Contains("commit 07").IsSelected(),
|
||||
Contains("fixup").Contains("commit 06").IsSelected(),
|
||||
Contains("<-- YOU ARE HERE --- commit 05"),
|
||||
Contains("commit 04"),
|
||||
).
|
||||
Press(keys.Commits.PickCommit).
|
||||
TopLines(
|
||||
Contains("pick").Contains("commit 10"),
|
||||
Contains("pick").Contains("commit 09"),
|
||||
Contains("pick").Contains("commit 08"),
|
||||
Contains("pick").Contains("commit 07").IsSelected(),
|
||||
Contains("pick").Contains("commit 06").IsSelected(),
|
||||
Contains("<-- YOU ARE HERE --- commit 05"),
|
||||
Contains("commit 04"),
|
||||
).
|
||||
Press(keys.Universal.Edit).
|
||||
TopLines(
|
||||
Contains("pick").Contains("commit 10"),
|
||||
Contains("pick").Contains("commit 09"),
|
||||
Contains("pick").Contains("commit 08"),
|
||||
Contains("edit").Contains("commit 07").IsSelected(),
|
||||
Contains("edit").Contains("commit 06").IsSelected(),
|
||||
Contains("<-- YOU ARE HERE --- commit 05"),
|
||||
Contains("commit 04"),
|
||||
).
|
||||
Press(keys.Commits.SquashDown).
|
||||
TopLines(
|
||||
Contains("pick").Contains("commit 10"),
|
||||
Contains("pick").Contains("commit 09"),
|
||||
Contains("pick").Contains("commit 08"),
|
||||
Contains("squash").Contains("commit 07").IsSelected(),
|
||||
Contains("squash").Contains("commit 06").IsSelected(),
|
||||
Contains("<-- YOU ARE HERE --- commit 05"),
|
||||
Contains("commit 04"),
|
||||
).
|
||||
Press(keys.Commits.MoveDownCommit).
|
||||
TopLines(
|
||||
Contains("pick").Contains("commit 10"),
|
||||
Contains("pick").Contains("commit 09"),
|
||||
Contains("pick").Contains("commit 08"),
|
||||
Contains("squash").Contains("commit 07").IsSelected(),
|
||||
Contains("squash").Contains("commit 06").IsSelected(),
|
||||
Contains("<-- YOU ARE HERE --- commit 05"),
|
||||
Contains("commit 04"),
|
||||
).
|
||||
Tap(func() {
|
||||
t.ExpectToast(Contains("Disabled: Cannot move any further"))
|
||||
}).
|
||||
Press(keys.Commits.MoveUpCommit).
|
||||
TopLines(
|
||||
Contains("pick").Contains("commit 10"),
|
||||
Contains("pick").Contains("commit 09"),
|
||||
Contains("squash").Contains("commit 07").IsSelected(),
|
||||
Contains("squash").Contains("commit 06").IsSelected(),
|
||||
Contains("pick").Contains("commit 08"),
|
||||
Contains("<-- YOU ARE HERE --- commit 05"),
|
||||
Contains("commit 04"),
|
||||
).
|
||||
Press(keys.Commits.MoveUpCommit).
|
||||
TopLines(
|
||||
Contains("pick").Contains("commit 10"),
|
||||
Contains("squash").Contains("commit 07").IsSelected(),
|
||||
Contains("squash").Contains("commit 06").IsSelected(),
|
||||
Contains("pick").Contains("commit 09"),
|
||||
Contains("pick").Contains("commit 08"),
|
||||
Contains("<-- YOU ARE HERE --- commit 05"),
|
||||
Contains("commit 04"),
|
||||
).
|
||||
Press(keys.Commits.MoveUpCommit).
|
||||
TopLines(
|
||||
Contains("squash").Contains("commit 07").IsSelected(),
|
||||
Contains("squash").Contains("commit 06").IsSelected(),
|
||||
Contains("pick").Contains("commit 10"),
|
||||
Contains("pick").Contains("commit 09"),
|
||||
Contains("pick").Contains("commit 08"),
|
||||
Contains("<-- YOU ARE HERE --- commit 05"),
|
||||
Contains("commit 04"),
|
||||
).
|
||||
Press(keys.Commits.MoveUpCommit).
|
||||
Tap(func() {
|
||||
t.ExpectToast(Contains("Disabled: Cannot move any further"))
|
||||
}).
|
||||
TopLines(
|
||||
Contains("squash").Contains("commit 07").IsSelected(),
|
||||
Contains("squash").Contains("commit 06").IsSelected(),
|
||||
Contains("pick").Contains("commit 10"),
|
||||
Contains("pick").Contains("commit 09"),
|
||||
Contains("pick").Contains("commit 08"),
|
||||
Contains("<-- YOU ARE HERE --- commit 05"),
|
||||
Contains("commit 04"),
|
||||
).
|
||||
// Verify we can't perform an action on a range that includes both
|
||||
// TODO and non-TODO commits
|
||||
NavigateToLine(Contains("commit 08")).
|
||||
Press(keys.Universal.RangeSelectDown).
|
||||
TopLines(
|
||||
Contains("squash").Contains("commit 07"),
|
||||
Contains("squash").Contains("commit 06"),
|
||||
Contains("pick").Contains("commit 10"),
|
||||
Contains("pick").Contains("commit 09"),
|
||||
Contains("pick").Contains("commit 08").IsSelected(),
|
||||
Contains("<-- YOU ARE HERE --- commit 05").IsSelected(),
|
||||
Contains("commit 04"),
|
||||
).
|
||||
Press(keys.Commits.MarkCommitAsFixup).
|
||||
Tap(func() {
|
||||
t.ExpectToast(Contains("Disabled: When rebasing, this action only works on a selection of TODO commits."))
|
||||
}).
|
||||
TopLines(
|
||||
Contains("squash").Contains("commit 07"),
|
||||
Contains("squash").Contains("commit 06"),
|
||||
Contains("pick").Contains("commit 10"),
|
||||
Contains("pick").Contains("commit 09"),
|
||||
Contains("pick").Contains("commit 08").IsSelected(),
|
||||
Contains("<-- YOU ARE HERE --- commit 05").IsSelected(),
|
||||
Contains("commit 04"),
|
||||
).
|
||||
// continue the rebase
|
||||
Tap(func() {
|
||||
t.Common().ContinueRebase()
|
||||
}).
|
||||
TopLines(
|
||||
Contains("commit 10"),
|
||||
Contains("commit 09"),
|
||||
Contains("commit 08"),
|
||||
Contains("commit 05"),
|
||||
// selected indexes are retained, though we may want to clear it
|
||||
// in future (not sure what the best behaviour is right now)
|
||||
Contains("commit 04").IsSelected(),
|
||||
Contains("commit 03").IsSelected(),
|
||||
)
|
||||
},
|
||||
})
|
@ -45,6 +45,9 @@ var Move = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
).
|
||||
// assert nothing happens upon trying to move beyond the last commit
|
||||
Press(keys.Commits.MoveDownCommit).
|
||||
Tap(func() {
|
||||
t.ExpectToast(Contains("Disabled: Cannot move any further"))
|
||||
}).
|
||||
Lines(
|
||||
Contains("commit 03"),
|
||||
Contains("commit 02"),
|
||||
@ -74,6 +77,9 @@ var Move = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
).
|
||||
// assert nothing happens upon trying to move beyond the first commit
|
||||
Press(keys.Commits.MoveUpCommit).
|
||||
Tap(func() {
|
||||
t.ExpectToast(Contains("Disabled: Cannot move any further"))
|
||||
}).
|
||||
Lines(
|
||||
Contains("commit 04").IsSelected(),
|
||||
Contains("commit 03"),
|
||||
|
@ -45,8 +45,11 @@ var MoveInRebase = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Contains("commit 03"),
|
||||
Contains("YOU ARE HERE").Contains("commit 01"),
|
||||
).
|
||||
Press(keys.Commits.MoveUpCommit).
|
||||
// assert we can't move past the top
|
||||
Press(keys.Commits.MoveUpCommit).
|
||||
Tap(func() {
|
||||
t.ExpectToast(Contains("Disabled: Cannot move any further"))
|
||||
}).
|
||||
Lines(
|
||||
Contains("commit 02").IsSelected(),
|
||||
Contains("commit 04"),
|
||||
@ -69,6 +72,9 @@ var MoveInRebase = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
).
|
||||
// assert we can't move past the bottom
|
||||
Press(keys.Commits.MoveDownCommit).
|
||||
Tap(func() {
|
||||
t.ExpectToast(Contains("Disabled: Cannot move any further"))
|
||||
}).
|
||||
Lines(
|
||||
Contains("commit 04"),
|
||||
Contains("commit 03"),
|
||||
|
@ -0,0 +1,155 @@
|
||||
package interactive_rebase
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var OutsideRebaseRangeSelect = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Do various things with range selection in the commits view when outside rebase",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.
|
||||
CreateNCommits(10)
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
TopLines(
|
||||
Contains("commit 10").IsSelected(),
|
||||
).
|
||||
Press(keys.Universal.RangeSelectDown).
|
||||
TopLines(
|
||||
Contains("commit 10").IsSelected(),
|
||||
Contains("commit 09").IsSelected(),
|
||||
Contains("commit 08"),
|
||||
).
|
||||
// Drop commits
|
||||
Press(keys.Universal.Remove).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Drop commit")).
|
||||
Content(Contains("Are you sure you want to drop the selected commit(s)?")).
|
||||
Confirm()
|
||||
}).
|
||||
TopLines(
|
||||
Contains("commit 08").IsSelected(),
|
||||
Contains("commit 07"),
|
||||
).
|
||||
Press(keys.Universal.RangeSelectDown).
|
||||
TopLines(
|
||||
Contains("commit 08").IsSelected(),
|
||||
Contains("commit 07").IsSelected(),
|
||||
Contains("commit 06"),
|
||||
).
|
||||
// Squash commits
|
||||
Press(keys.Commits.SquashDown).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Squash")).
|
||||
Content(Contains("Are you sure you want to squash the selected commit(s) into the commit below?")).
|
||||
Confirm()
|
||||
}).
|
||||
TopLines(
|
||||
Contains("commit 06").IsSelected(),
|
||||
Contains("commit 05"),
|
||||
Contains("commit 04"),
|
||||
).
|
||||
// Verify commit messages are concatenated
|
||||
Tap(func() {
|
||||
t.Views().Main().
|
||||
ContainsLines(
|
||||
Contains("commit 06"),
|
||||
AnyString(),
|
||||
Contains("commit 07"),
|
||||
AnyString(),
|
||||
Contains("commit 08"),
|
||||
)
|
||||
}).
|
||||
// Fixup commits
|
||||
Press(keys.Universal.RangeSelectDown).
|
||||
TopLines(
|
||||
Contains("commit 06").IsSelected(),
|
||||
Contains("commit 05").IsSelected(),
|
||||
Contains("commit 04"),
|
||||
).
|
||||
Press(keys.Commits.MarkCommitAsFixup).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Fixup")).
|
||||
Content(Contains("Are you sure you want to 'fixup' the selected commit(s) into the commit below?")).
|
||||
Confirm()
|
||||
}).
|
||||
TopLines(
|
||||
Contains("commit 04").IsSelected(),
|
||||
Contains("commit 03"),
|
||||
Contains("commit 02"),
|
||||
).
|
||||
// Verify commit messages are dropped
|
||||
Tap(func() {
|
||||
t.Views().Main().
|
||||
Content(
|
||||
Contains("commit 04").
|
||||
DoesNotContain("commit 06").
|
||||
DoesNotContain("commit 05"),
|
||||
)
|
||||
}).
|
||||
Press(keys.Universal.RangeSelectDown).
|
||||
TopLines(
|
||||
Contains("commit 04").IsSelected(),
|
||||
Contains("commit 03").IsSelected(),
|
||||
Contains("commit 02"),
|
||||
).
|
||||
// Move commits
|
||||
Press(keys.Commits.MoveDownCommit).
|
||||
TopLines(
|
||||
Contains("commit 02"),
|
||||
Contains("commit 04").IsSelected(),
|
||||
Contains("commit 03").IsSelected(),
|
||||
Contains("commit 01"),
|
||||
).
|
||||
Press(keys.Commits.MoveDownCommit).
|
||||
TopLines(
|
||||
Contains("commit 02"),
|
||||
Contains("commit 01"),
|
||||
Contains("commit 04").IsSelected(),
|
||||
Contains("commit 03").IsSelected(),
|
||||
).
|
||||
Press(keys.Commits.MoveDownCommit).
|
||||
TopLines(
|
||||
Contains("commit 02"),
|
||||
Contains("commit 01"),
|
||||
Contains("commit 04").IsSelected(),
|
||||
Contains("commit 03").IsSelected(),
|
||||
).
|
||||
Tap(func() {
|
||||
t.ExpectToast(Contains("Disabled: Cannot move any further"))
|
||||
}).
|
||||
Press(keys.Commits.MoveUpCommit).
|
||||
TopLines(
|
||||
Contains("commit 02"),
|
||||
Contains("commit 04").IsSelected(),
|
||||
Contains("commit 03").IsSelected(),
|
||||
Contains("commit 01"),
|
||||
).
|
||||
Press(keys.Commits.MoveUpCommit).
|
||||
TopLines(
|
||||
Contains("commit 04").IsSelected(),
|
||||
Contains("commit 03").IsSelected(),
|
||||
Contains("commit 02"),
|
||||
Contains("commit 01"),
|
||||
).
|
||||
Press(keys.Commits.MoveUpCommit).
|
||||
Tap(func() {
|
||||
t.ExpectToast(Contains("Disabled: Cannot move any further"))
|
||||
}).
|
||||
TopLines(
|
||||
Contains("commit 04").IsSelected(),
|
||||
Contains("commit 03").IsSelected(),
|
||||
Contains("commit 02"),
|
||||
Contains("commit 01"),
|
||||
)
|
||||
},
|
||||
})
|
@ -27,7 +27,7 @@ var SquashDownSecondCommit = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Squash")).
|
||||
Content(Equals("Are you sure you want to squash this commit into the commit below?")).
|
||||
Content(Equals("Are you sure you want to squash the selected commit(s) into the commit below?")).
|
||||
Confirm()
|
||||
}).
|
||||
Lines(
|
||||
|
@ -161,9 +161,11 @@ var tests = []*components.IntegrationTest{
|
||||
interactive_rebase.EditTheConflCommit,
|
||||
interactive_rebase.FixupFirstCommit,
|
||||
interactive_rebase.FixupSecondCommit,
|
||||
interactive_rebase.MidRebaseRangeSelect,
|
||||
interactive_rebase.Move,
|
||||
interactive_rebase.MoveInRebase,
|
||||
interactive_rebase.MoveWithCustomCommentChar,
|
||||
interactive_rebase.OutsideRebaseRangeSelect,
|
||||
interactive_rebase.PickRescheduled,
|
||||
interactive_rebase.QuickStart,
|
||||
interactive_rebase.Rebase,
|
||||
|
@ -24,8 +24,8 @@ var UndoCheckoutAndDrop = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
|
||||
confirmCommitDrop := func() {
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Delete commit")).
|
||||
Content(Equals("Are you sure you want to delete this commit?")).
|
||||
Title(Equals("Drop commit")).
|
||||
Content(Equals("Are you sure you want to drop the selected commit(s)?")).
|
||||
Confirm()
|
||||
}
|
||||
|
||||
|
@ -19,8 +19,8 @@ var UndoDrop = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
confirmCommitDrop := func() {
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Delete commit")).
|
||||
Content(Equals("Are you sure you want to delete this commit?")).
|
||||
Title(Equals("Drop commit")).
|
||||
Content(Equals("Are you sure you want to drop the selected commit(s)?")).
|
||||
Confirm()
|
||||
}
|
||||
|
||||
|
@ -9,27 +9,46 @@ import (
|
||||
"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 {
|
||||
type Todo struct {
|
||||
Sha string
|
||||
Action todo.TodoCommand
|
||||
}
|
||||
|
||||
// In order to change a TODO in git-rebase-todo, we need to specify the old action,
|
||||
// because sometimes the same sha appears multiple times in the file (e.g. in a pick
|
||||
// and later in a merge)
|
||||
type TodoChange struct {
|
||||
Sha string
|
||||
OldAction todo.TodoCommand
|
||||
NewAction todo.TodoCommand
|
||||
}
|
||||
|
||||
// Read a git-rebase-todo file, change the actions for the given commits,
|
||||
// and write it back
|
||||
func EditRebaseTodo(filePath string, changes []TodoChange, commentChar byte) error {
|
||||
todos, err := ReadRebaseTodoFile(filePath, commentChar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
matchCount := 0
|
||||
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)
|
||||
// This is a nested loop, but it's ok because the number of todos should be small
|
||||
for _, change := range changes {
|
||||
if t.Command == change.OldAction && equalShas(t.Commit, change.Sha) {
|
||||
matchCount++
|
||||
t.Command = change.NewAction
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Should never get here
|
||||
return fmt.Errorf("Todo %s not found in git-rebase-todo", sha)
|
||||
if matchCount < len(changes) {
|
||||
// Should never get here
|
||||
return fmt.Errorf("Some todos not found in git-rebase-todo")
|
||||
}
|
||||
|
||||
return WriteRebaseTodoFile(filePath, todos, commentChar)
|
||||
}
|
||||
|
||||
func equalShas(a, b string) bool {
|
||||
@ -73,24 +92,24 @@ func PrependStrToTodoFile(filePath string, linesToPrepend []byte) error {
|
||||
return os.WriteFile(filePath, linesToPrepend, 0o644)
|
||||
}
|
||||
|
||||
func MoveTodoDown(fileName string, sha string, action todo.TodoCommand, commentChar byte) error {
|
||||
func MoveTodosDown(fileName string, todosToMove []Todo, commentChar byte) error {
|
||||
todos, err := ReadRebaseTodoFile(fileName, commentChar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rearrangedTodos, err := moveTodoDown(todos, sha, action)
|
||||
rearrangedTodos, err := moveTodosDown(todos, todosToMove)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return WriteRebaseTodoFile(fileName, rearrangedTodos, commentChar)
|
||||
}
|
||||
|
||||
func MoveTodoUp(fileName string, sha string, action todo.TodoCommand, commentChar byte) error {
|
||||
func MoveTodosUp(fileName string, todosToMove []Todo, commentChar byte) error {
|
||||
todos, err := ReadRebaseTodoFile(fileName, commentChar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rearrangedTodos, err := moveTodoUp(todos, sha, action)
|
||||
rearrangedTodos, err := moveTodosUp(todos, todosToMove)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -102,6 +121,11 @@ func moveTodoDown(todos []todo.Todo, sha string, action todo.TodoCommand) ([]tod
|
||||
return lo.Reverse(rearrangedTodos), err
|
||||
}
|
||||
|
||||
func moveTodosDown(todos []todo.Todo, todosToMove []Todo) ([]todo.Todo, error) {
|
||||
rearrangedTodos, err := moveTodosUp(lo.Reverse(todos), lo.Reverse(todosToMove))
|
||||
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
|
||||
@ -134,6 +158,19 @@ func moveTodoUp(todos []todo.Todo, sha string, action todo.TodoCommand) ([]todo.
|
||||
return rearrangedTodos, nil
|
||||
}
|
||||
|
||||
func moveTodosUp(todos []todo.Todo, todosToMove []Todo) ([]todo.Todo, error) {
|
||||
for _, todoToMove := range todosToMove {
|
||||
var newTodos []todo.Todo
|
||||
newTodos, err := moveTodoUp(todos, todoToMove.Sha, todoToMove.Action)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
todos = newTodos
|
||||
}
|
||||
|
||||
return todos, nil
|
||||
}
|
||||
|
||||
func MoveFixupCommitDown(fileName string, originalSha string, fixupSha string, commentChar byte) error {
|
||||
todos, err := ReadRebaseTodoFile(fileName, commentChar)
|
||||
if err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user