Skip to content

Commit

Permalink
Support range select for rebase actions (jesseduffield#3232)
Browse files Browse the repository at this point in the history
- **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
-->
  • Loading branch information
jesseduffield committed Jan 23, 2024
2 parents a67ad44 + 41d5f4d commit 5511cc1
Show file tree
Hide file tree
Showing 71 changed files with 1,084 additions and 528 deletions.
74 changes: 45 additions & 29 deletions pkg/app/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ const (

DaemonKindExitImmediately
DaemonKindCherryPick
DaemonKindMoveTodoUp
DaemonKindMoveTodoDown
DaemonKindMoveTodosUp
DaemonKindMoveTodosDown
DaemonKindInsertBreak
DaemonKindChangeTodoActions
DaemonKindMoveFixupCommitDown
Expand All @@ -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],
}

Expand Down Expand Up @@ -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())
})
}

Expand Down Expand Up @@ -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())
})
}

Expand Down
94 changes: 54 additions & 40 deletions pkg/commands/git_commands/rebase.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

sha := commits[index].Sha
func (self *RebaseCommands) MoveCommitsUp(commits []*models.Commit, startIdx int, endIdx int) error {
baseShaOrRoot := getBaseShaOrRoot(commits, endIdx+1)

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{
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
9 changes: 0 additions & 9 deletions pkg/gui/context/branches_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
9 changes: 0 additions & 9 deletions pkg/gui/context/commit_files_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()}
}
4 changes: 2 additions & 2 deletions pkg/gui/context/filtered_list_view_model.go
Original file line number Diff line number Diff line change
@@ -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]{
Expand Down
Loading

0 comments on commit 5511cc1

Please sign in to comment.