From 43d3f2bcb6094293156841f6670577720bab2314 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 20 Mar 2022 16:19:27 +1100 Subject: [PATCH] refactor todo file generation --- go.mod | 2 +- go.sum | 4 +- pkg/commands/git_commands/patch.go | 18 ++--- pkg/commands/git_commands/rebase.go | 79 ++++++++++++------- .../jesseduffield/generics/slices/slices.go | 26 ++++++ vendor/modules.txt | 2 +- 6 files changed, 91 insertions(+), 40 deletions(-) diff --git a/go.mod b/go.mod index fd00f19d8..29d28826a 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/gookit/color v1.4.2 github.com/imdario/mergo v0.3.11 github.com/integrii/flaggy v1.4.0 - github.com/jesseduffield/generics v0.0.0-20220319230408-6eaa96457df2 + github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4 github.com/jesseduffield/gocui v0.3.1-0.20220227022729-69f0c798eec8 github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e diff --git a/go.sum b/go.sum index 2f8f8e890..1ae960d8e 100644 --- a/go.sum +++ b/go.sum @@ -66,8 +66,8 @@ github.com/integrii/flaggy v1.4.0 h1:A1x7SYx4jqu5NSrY14z8Z+0UyX2S5ygfJJrfolWR3zM github.com/integrii/flaggy v1.4.0/go.mod h1:tnTxHeTJbah0gQ6/K0RW0J7fMUBk9MCF5blhm43LNpI= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jesseduffield/generics v0.0.0-20220319230408-6eaa96457df2 h1:nGS5ysWioxYaPzwuEK3b4NKzBnNhQjiD1fK3bkn43cQ= -github.com/jesseduffield/generics v0.0.0-20220319230408-6eaa96457df2/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk= +github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8TIcC6Y4RI+1ZbJDOHfGJ570tPeYVCqo7/tws= +github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk= github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4 h1:GOQrmaE8i+KEdB8NzAegKYd4tPn/inM0I1uo0NXFerg= github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o= github.com/jesseduffield/gocui v0.3.1-0.20220227022729-69f0c798eec8 h1:9N08i5kjvOfkzMj6THmIM110wPTQLdVYEOHMHT2DFiI= diff --git a/pkg/commands/git_commands/patch.go b/pkg/commands/git_commands/patch.go index 8f9ce5784..a805c7f21 100644 --- a/pkg/commands/git_commands/patch.go +++ b/pkg/commands/git_commands/patch.go @@ -105,16 +105,16 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s } baseIndex := sourceCommitIdx + 1 - todo := "" - for i, commit := range commits[0:baseIndex] { - a := "pick" - if i == sourceCommitIdx || i == destinationCommitIdx { - a = "edit" - } - todo = a + " " + commit.Sha + " " + commit.Name + "\n" + todo - } - err := self.rebase.PrepareInteractiveRebaseCommand(commits[baseIndex].Sha, todo, true).Run() + todoLines := self.rebase.BuildTodoLines(commits[0:baseIndex], func(commit *models.Commit, i int) string { + if i == sourceCommitIdx || i == destinationCommitIdx { + return "edit" + } else { + return "pick" + } + }) + + err := self.rebase.PrepareInteractiveRebaseCommand(commits[baseIndex].Sha, todoLines, true).Run() if err != nil { return err } diff --git a/pkg/commands/git_commands/rebase.go b/pkg/commands/git_commands/rebase.go index 71c8b0e63..ab7963c44 100644 --- a/pkg/commands/git_commands/rebase.go +++ b/pkg/commands/git_commands/rebase.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/go-errors/errors" + "github.com/jesseduffield/generics/slices" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/oscommands" ) @@ -52,7 +53,7 @@ func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, me } func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index int) (oscommands.ICmdObj, error) { - todo, sha, err := self.GenerateGenericRebaseTodo(commits, index, "reword") + todo, sha, err := self.BuildSingleActionTodo(commits, index, "reword") if err != nil { return nil, err } @@ -67,17 +68,15 @@ func (self *RebaseCommands) MoveCommitDown(commits []*models.Commit, index int) return errors.New(self.Tr.NoRoom) } - todo := "" orderedCommits := append(commits[0:index], commits[index+1], commits[index]) - for _, commit := range orderedCommits { - todo = "pick " + commit.Sha + " " + commit.Name + "\n" + todo - } - return self.PrepareInteractiveRebaseCommand(commits[index+2].Sha, todo, true).Run() + todoLines := self.BuildTodoLinesSingleAction(orderedCommits, "pick") + + return self.PrepareInteractiveRebaseCommand(commits[index+2].Sha, todoLines, true).Run() } func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index int, action string) error { - todo, sha, err := self.GenerateGenericRebaseTodo(commits, index, action) + todo, sha, err := self.BuildSingleActionTodo(commits, index, action) if err != nil { return err } @@ -88,7 +87,8 @@ func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index in // PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase // we tell git to run lazygit to edit the todo list, and we pass the client // lazygit a todo string to write to the todo file -func (self *RebaseCommands) PrepareInteractiveRebaseCommand(baseSha string, todo string, overrideEditor bool) oscommands.ICmdObj { +func (self *RebaseCommands) PrepareInteractiveRebaseCommand(baseSha string, todoLines []TodoLine, overrideEditor bool) oscommands.ICmdObj { + todo := self.buildTodo(todoLines) ex := oscommands.GetLazygitPath() debug := "FALSE" @@ -124,38 +124,37 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(baseSha string, todo return cmdObj } -func (self *RebaseCommands) GenerateGenericRebaseTodo(commits []*models.Commit, actionIndex int, action string) (string, string, error) { +// produces TodoLines where every commit is picked (or dropped for merge commits) except for the commit at the given index, which +// will have the given action applied to it. +func (self *RebaseCommands) BuildSingleActionTodo(commits []*models.Commit, actionIndex int, action string) ([]TodoLine, string, error) { baseIndex := actionIndex + 1 if len(commits) <= baseIndex { - return "", "", errors.New(self.Tr.CannotRebaseOntoFirstCommit) + return nil, "", errors.New(self.Tr.CannotRebaseOntoFirstCommit) } if action == "squash" || action == "fixup" { baseIndex++ if len(commits) <= baseIndex { - return "", "", errors.New(self.Tr.CannotSquashOntoSecondCommit) + return nil, "", errors.New(self.Tr.CannotSquashOntoSecondCommit) } } - todo := "" - for i, commit := range commits[0:baseIndex] { - var commitAction string + todoLines := self.BuildTodoLines(commits[0:baseIndex], func(commit *models.Commit, i int) string { if i == actionIndex { - commitAction = action + return action } else if commit.IsMerge() { // your typical interactive rebase will actually drop merge commits by default. Damn git CLI, you scary! // doing this means we don't need to worry about rebasing over merges which always causes problems. // you typically shouldn't be doing rebases that pass over merge commits anyway. - commitAction = "drop" + return "drop" } else { - commitAction = "pick" + return "pick" } - todo = commitAction + " " + commit.Sha + " " + commit.Name + "\n" + todo - } + }) - return todo, commits[baseIndex].Sha, nil + return todoLines, commits[baseIndex].Sha, nil } // AmendTo amends the given commit with whatever files are staged @@ -244,7 +243,7 @@ func (self *RebaseCommands) BeginInteractiveRebaseForCommit(commits []*models.Co return errors.New(self.Tr.DisabledForGPG) } - todo, sha, err := self.GenerateGenericRebaseTodo(commits, commitIndex, "edit") + todo, sha, err := self.BuildSingleActionTodo(commits, commitIndex, "edit") if err != nil { return err } @@ -254,7 +253,7 @@ func (self *RebaseCommands) BeginInteractiveRebaseForCommit(commits []*models.Co // RebaseBranch interactive rebases onto a branch func (self *RebaseCommands) RebaseBranch(branchName string) error { - return self.PrepareInteractiveRebaseCommand(branchName, "", false).Run() + return self.PrepareInteractiveRebaseCommand(branchName, nil, false).Run() } func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj { @@ -336,10 +335,36 @@ func (self *RebaseCommands) DiscardOldFileChanges(commits []*models.Commit, comm // CherryPickCommits begins an interactive rebase with the given shas being cherry picked onto HEAD func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error { - todo := "" - for _, commit := range commits { - todo = "pick " + commit.Sha + " " + commit.Name + "\n" + todo - } + todoLines := self.BuildTodoLinesSingleAction(commits, "pick") - return self.PrepareInteractiveRebaseCommand("HEAD", todo, false).Run() + return self.PrepareInteractiveRebaseCommand("HEAD", todoLines, false).Run() +} + +func (self *RebaseCommands) buildTodo(todoLines []TodoLine) string { + lines := slices.Map(todoLines, func(todoLine TodoLine) string { + return todoLine.ToString() + }) + + return strings.Join(slices.Reverse(lines), "") +} + +func (self *RebaseCommands) BuildTodoLines(commits []*models.Commit, f func(*models.Commit, int) string) []TodoLine { + return slices.MapWithIndex(commits, func(commit *models.Commit, i int) TodoLine { + return TodoLine{Action: f(commit, i), Commit: commit} + }) +} + +func (self *RebaseCommands) BuildTodoLinesSingleAction(commits []*models.Commit, action string) []TodoLine { + return self.BuildTodoLines(commits, func(commit *models.Commit, i int) string { + return action + }) +} + +type TodoLine struct { + Action string + Commit *models.Commit +} + +func (self *TodoLine) ToString() string { + return self.Action + " " + self.Commit.Sha + " " + self.Commit.Name + "\n" } diff --git a/vendor/github.com/jesseduffield/generics/slices/slices.go b/vendor/github.com/jesseduffield/generics/slices/slices.go index 18ad4694f..5bfb19968 100644 --- a/vendor/github.com/jesseduffield/generics/slices/slices.go +++ b/vendor/github.com/jesseduffield/generics/slices/slices.go @@ -256,6 +256,7 @@ func Remove[T any](slice []T, index int) []T { return slices.Delete(slice, index, index+1) } +// Removes the element at the 'fromIndex' and then inserts it at 'toIndex'. // Operates on the input slice. Expected use is to reassign the result to the input slice. func Move[T any](slice []T, fromIndex int, toIndex int) []T { item := slice[fromIndex] @@ -263,6 +264,12 @@ func Move[T any](slice []T, fromIndex int, toIndex int) []T { return slices.Insert(slice, toIndex, item) } +// Swaps two elements at the given indices. +// Operates on the input slice. +func Swap[T any](slice []T, index1 int, index2 int) { + slice[index1], slice[index2] = slice[index2], slice[index1] +} + // Similar to Append but we leave the original slice untouched and return a new slice func Concat[T any](slice []T, values ...T) []T { newSlice := make([]T, 0, len(slice)+len(values)) @@ -346,6 +353,17 @@ func Find[T any](slice []T, f func(T) bool) (T, bool) { return zero[T](), false } +// Sometimes you need to find an element and then map it to some other value based on +// information you obtained while finding it. This function lets you do that +func FindMap[T any, V any](slice []T, f func(T) (V, bool)) (V, bool) { + for _, element := range slice { + if value, ok := f(element); ok { + return value, true + } + } + return zero[V](), false +} + func ForEach[T any](slice []T, f func(T)) { for _, element := range slice { f(element) @@ -376,6 +394,14 @@ func TryForEachWithIndex[T any](slice []T, f func(T, int) error) error { return nil } +func Sum[T constraints.Ordered](i []T) T { + sum := zero[T]() + for _, value := range i { + sum += value + } + return sum +} + func zero[T any]() T { var value T return value diff --git a/vendor/modules.txt b/vendor/modules.txt index e6dc129fc..77bebf7a1 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -120,7 +120,7 @@ github.com/integrii/flaggy # github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 ## explicit github.com/jbenet/go-context/io -# github.com/jesseduffield/generics v0.0.0-20220319230408-6eaa96457df2 +# github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 ## explicit; go 1.18 github.com/jesseduffield/generics/maps github.com/jesseduffield/generics/set