Add command to squash all fixups in the current branch

To do that, change the "Apply fixup commits" command to show a menu with the two
choices "in current branch" and "above the selected commit"; we make "in current
branch" the default, as it's the more useful one most of the time, even though
it is a breaking change for those who are used to "shift-S enter" meaning
"squash above selected".
This commit is contained in:
Stefan Haller 2024-01-23 17:03:44 +01:00
parent 24e79a057c
commit b133318b40
14 changed files with 133 additions and 31 deletions

View file

@ -89,7 +89,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
If you would instead like to start an interactive rebase from the selected commit, press `e`. | If you would instead like to start an interactive rebase from the selected commit, press `e`. |
| `` p `` | Pick | Mark the selected commit to be picked (when mid-rebase). This means that the commit will be retained upon continuing the rebase. | | `` p `` | Pick | Mark the selected commit to be picked (when mid-rebase). This means that the commit will be retained upon continuing the rebase. |
| `` F `` | Create fixup commit | Create 'fixup!' commit for the selected commit. Later on, you can press `S` on this same commit to apply all above fixup commits. | | `` F `` | Create fixup commit | Create 'fixup!' commit for the selected commit. Later on, you can press `S` on this same commit to apply all above fixup commits. |
| `` S `` | Apply fixup commits | Squash all 'fixup!' commits above selected commit (autosquash). | | `` S `` | Apply fixup commits | Squash all 'fixup!' commits, either above the selected commit, or all in current branch (autosquash). |
| `` <c-j> `` | Move commit down one | | | `` <c-j> `` | Move commit down one | |
| `` <c-k> `` | Move commit up one | | | `` <c-k> `` | Move commit up one | |
| `` V `` | Paste (cherry-pick) | | | `` V `` | Paste (cherry-pick) | |

View file

@ -106,7 +106,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
If you would instead like to start an interactive rebase from the selected commit, press `e`. | If you would instead like to start an interactive rebase from the selected commit, press `e`. |
| `` p `` | Pick | Mark the selected commit to be picked (when mid-rebase). This means that the commit will be retained upon continuing the rebase. | | `` p `` | Pick | Mark the selected commit to be picked (when mid-rebase). This means that the commit will be retained upon continuing the rebase. |
| `` F `` | Create fixup commit | このコミットに対するfixupコミットを作成 | | `` F `` | Create fixup commit | このコミットに対するfixupコミットを作成 |
| `` S `` | Apply fixup commits | Squash all 'fixup!' commits above selected commit (autosquash). | | `` S `` | Apply fixup commits | Squash all 'fixup!' commits, either above the selected commit, or all in current branch (autosquash). |
| `` <c-j> `` | コミットを1つ下に移動 | | | `` <c-j> `` | コミットを1つ下に移動 | |
| `` <c-k> `` | コミットを1つ上に移動 | | | `` <c-k> `` | コミットを1つ上に移動 | |
| `` V `` | コミットを貼り付け (cherry-pick) | | | `` V `` | コミットを貼り付け (cherry-pick) | |

View file

@ -167,13 +167,13 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
}, },
{ {
Key: opts.GetKey(opts.Config.Commits.SquashAboveCommits), Key: opts.GetKey(opts.Config.Commits.SquashAboveCommits),
Handler: self.withItem(self.squashAllAboveFixupCommits), Handler: self.squashFixupCommits,
GetDisabledReason: self.require( GetDisabledReason: self.require(
self.notMidRebase(self.c.Tr.AlreadyRebasing), self.notMidRebase(self.c.Tr.AlreadyRebasing),
self.singleItemSelected(),
), ),
Description: self.c.Tr.SquashAboveCommits, Description: self.c.Tr.SquashAboveCommits,
Tooltip: self.c.Tr.SquashAboveCommitsTooltip, Tooltip: self.c.Tr.SquashAboveCommitsTooltip,
OpensMenu: true,
}, },
{ {
Key: opts.GetKey(opts.Config.Commits.MoveDownCommit), Key: opts.GetKey(opts.Config.Commits.MoveDownCommit),
@ -816,25 +816,62 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
}) })
} }
func (self *LocalCommitsController) squashAllAboveFixupCommits(commit *models.Commit) error { func (self *LocalCommitsController) squashFixupCommits() error {
prompt := utils.ResolvePlaceholderString( return self.c.Menu(types.CreateMenuOptions{
self.c.Tr.SureSquashAboveCommits, Title: self.c.Tr.SquashAboveCommits,
map[string]string{"commit": commit.Sha}, Items: []*types.MenuItem{
) {
Label: self.c.Tr.SquashCommitsInCurrentBranch,
return self.c.Confirm(types.ConfirmOpts{ OnPress: self.squashAllFixupsInCurrentBranch,
Title: self.c.Tr.SquashAboveCommits, DisabledReason: self.canFindCommitForSquashFixupsInCurrentBranch(),
Prompt: prompt, Key: 'b',
HandleConfirm: func() error { Tooltip: self.c.Tr.SquashCommitsInCurrentBranchTooltip,
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func(gocui.Task) error { },
self.c.LogAction(self.c.Tr.Actions.SquashAllAboveFixupCommits) {
err := self.c.Git().Rebase.SquashAllAboveFixupCommits(commit) Label: self.c.Tr.SquashCommitsAboveSelectedCommit,
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err) OnPress: self.withItem(self.squashAllFixupsAboveSelectedCommit),
}) DisabledReason: self.singleItemSelected()(),
Key: 'a',
Tooltip: self.c.Tr.SquashCommitsAboveSelectedTooltip,
},
}, },
}) })
} }
func (self *LocalCommitsController) squashAllFixupsAboveSelectedCommit(commit *models.Commit) error {
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.SquashAllAboveFixupCommits)
err := self.c.Git().Rebase.SquashAllAboveFixupCommits(commit)
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
})
}
func (self *LocalCommitsController) squashAllFixupsInCurrentBranch() error {
commit, err := self.findCommitForSquashFixupsInCurrentBranch()
if err != nil {
return self.c.Error(err)
}
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.SquashAllAboveFixupCommits)
err := self.c.Git().Rebase.SquashAllAboveFixupCommits(commit)
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
})
}
func (self *LocalCommitsController) findCommitForSquashFixupsInCurrentBranch() (*models.Commit, error) {
commits := self.c.Model().Commits
_, index, ok := lo.FindIndexOf(commits, func(c *models.Commit) bool {
return c.IsMerge() || c.Status == models.StatusMerged
})
if !ok || index == 0 {
return nil, errors.New(self.c.Tr.CannotSquashCommitsInCurrentBranch)
}
return commits[index-1], nil
}
func (self *LocalCommitsController) createTag(commit *models.Commit) error { func (self *LocalCommitsController) createTag(commit *models.Commit) error {
return self.c.Helpers().Tags.OpenCreateTagPrompt(commit.Sha, func() {}) return self.c.Helpers().Tags.OpenCreateTagPrompt(commit.Sha, func() {})
} }
@ -1019,6 +1056,14 @@ func (self *LocalCommitsController) canFindCommitForQuickStart() *types.Disabled
return nil return nil
} }
func (self *LocalCommitsController) canFindCommitForSquashFixupsInCurrentBranch() *types.DisabledReason {
if _, err := self.findCommitForSquashFixupsInCurrentBranch(); err != nil {
return &types.DisabledReason{Text: err.Error()}
}
return nil
}
func (self *LocalCommitsController) canSquashOrFixup(_selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason { func (self *LocalCommitsController) canSquashOrFixup(_selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason {
if endIdx >= len(self.c.Model().Commits)-1 { if endIdx >= len(self.c.Model().Commits)-1 {
return &types.DisabledReason{Text: self.c.Tr.CannotSquashOrFixupFirstCommit} return &types.DisabledReason{Text: self.c.Tr.CannotSquashOrFixupFirstCommit}

View file

@ -254,7 +254,6 @@ func chineseTranslationSet() TranslationSet {
ViewResetOptions: `查看重置选项`, ViewResetOptions: `查看重置选项`,
CreateFixupCommit: `为此提交创建修正`, CreateFixupCommit: `为此提交创建修正`,
SquashAboveCommitsTooltip: `压缩在所选提交之上的所有“fixup!”提交(自动压缩)`, SquashAboveCommitsTooltip: `压缩在所选提交之上的所有“fixup!”提交(自动压缩)`,
SureSquashAboveCommits: `您确定要压缩在 {{.commit}} 之上的所有“fixup!”提交吗?`,
CreateFixupCommitTooltip: `创建修正提交`, CreateFixupCommitTooltip: `创建修正提交`,
SureCreateFixupCommit: `您确定要对 {{.commit}} 创建修正提交吗?`, SureCreateFixupCommit: `您确定要对 {{.commit}} 创建修正提交吗?`,
ExecuteCustomCommand: "执行自定义命令", ExecuteCustomCommand: "执行自定义命令",

View file

@ -217,7 +217,6 @@ func dutchTranslationSet() TranslationSet {
HardReset: "Harde reset", HardReset: "Harde reset",
CreateFixupCommit: `Creëer fixup commit voor deze commit`, CreateFixupCommit: `Creëer fixup commit voor deze commit`,
SquashAboveCommitsTooltip: `Squash bovenstaande commits`, SquashAboveCommitsTooltip: `Squash bovenstaande commits`,
SureSquashAboveCommits: `Weet je zeker dat je alles wil squash/fixup! voor de bovenstaand commits {{.commit}}?`,
CreateFixupCommitTooltip: `Creëer fixup commit`, CreateFixupCommitTooltip: `Creëer fixup commit`,
SureCreateFixupCommit: `Weet je zeker dat je een fixup wil maken! commit voor commit {{.commit}}?`, SureCreateFixupCommit: `Weet je zeker dat je een fixup wil maken! commit voor commit {{.commit}}?`,
ExecuteCustomCommand: "Voer aangepaste commando uit", ExecuteCustomCommand: "Voer aangepaste commando uit",

View file

@ -386,8 +386,12 @@ type TranslationSet struct {
CreateFixupCommitDescription string CreateFixupCommitDescription string
CreateFixupCommitTooltip string CreateFixupCommitTooltip string
SquashAboveCommitsTooltip string SquashAboveCommitsTooltip string
SquashCommitsAboveSelectedTooltip string
SquashCommitsInCurrentBranchTooltip string
SquashAboveCommits string SquashAboveCommits string
SureSquashAboveCommits string SquashCommitsInCurrentBranch string
SquashCommitsAboveSelectedCommit string
CannotSquashCommitsInCurrentBranch string
SureCreateFixupCommit string SureCreateFixupCommit string
ExecuteCustomCommand string ExecuteCustomCommand string
ExecuteCustomCommandTooltip string ExecuteCustomCommandTooltip string
@ -1322,8 +1326,12 @@ func EnglishTranslationSet() TranslationSet {
CreateFixupCommitDescription: `Create fixup commit`, CreateFixupCommitDescription: `Create fixup commit`,
CreateFixupCommitTooltip: "Create 'fixup!' commit for the selected commit. Later on, you can press `{{.squashAbove}}` on this same commit to apply all above fixup commits.", CreateFixupCommitTooltip: "Create 'fixup!' commit for the selected commit. Later on, you can press `{{.squashAbove}}` on this same commit to apply all above fixup commits.",
SquashAboveCommits: "Apply fixup commits", SquashAboveCommits: "Apply fixup commits",
SquashAboveCommitsTooltip: `Squash all 'fixup!' commits above selected commit (autosquash).`, SquashAboveCommitsTooltip: `Squash all 'fixup!' commits, either above the selected commit, or all in current branch (autosquash).`,
SureSquashAboveCommits: `Are you sure you want to squash all fixup! commits above {{.commit}}?`, SquashCommitsAboveSelectedTooltip: `Squash all 'fixup!' commits above the selected commit (autosquash).`,
SquashCommitsInCurrentBranchTooltip: `Squash all 'fixup!' commits in the current branch (autosquash).`,
SquashCommitsInCurrentBranch: "In current branch",
SquashCommitsAboveSelectedCommit: "Above the selected commit",
CannotSquashCommitsInCurrentBranch: "Cannot squash commits in current branch: the HEAD commit is a merge commit or is present on the main branch.",
CreateFixupCommit: `Create fixup commit`, CreateFixupCommit: `Create fixup commit`,
SureCreateFixupCommit: `Are you sure you want to create a fixup! commit for commit {{.commit}}?`, SureCreateFixupCommit: `Are you sure you want to create a fixup! commit for commit {{.commit}}?`,
ExecuteCustomCommand: "Execute custom command", ExecuteCustomCommand: "Execute custom command",

View file

@ -263,7 +263,6 @@ func japaneseTranslationSet() TranslationSet {
CreateFixupCommitTooltip: `このコミットに対するfixupコミットを作成`, CreateFixupCommitTooltip: `このコミットに対するfixupコミットを作成`,
// LcSquashAboveCommits: `squash all 'fixup!' commits above selected commit (autosquash)`, // LcSquashAboveCommits: `squash all 'fixup!' commits above selected commit (autosquash)`,
// SquashAboveCommits: `Squash all 'fixup!' commits above selected commit (autosquash)`, // SquashAboveCommits: `Squash all 'fixup!' commits above selected commit (autosquash)`,
SureSquashAboveCommits: `{{.commit}}に対するすべての fixup! コミットをsquashします。よろしいですか?`,
CreateFixupCommit: `Fixupコミットを作成`, CreateFixupCommit: `Fixupコミットを作成`,
SureCreateFixupCommit: `{{.commit}} に対する fixup! コミットを作成します。よろしいですか?`, SureCreateFixupCommit: `{{.commit}} に対する fixup! コミットを作成します。よろしいですか?`,
ExecuteCustomCommand: "カスタムコマンドを実行", ExecuteCustomCommand: "カスタムコマンドを実行",

View file

@ -258,7 +258,6 @@ func koreanTranslationSet() TranslationSet {
ViewResetOptions: `View reset options`, ViewResetOptions: `View reset options`,
CreateFixupCommitTooltip: `Create fixup commit for this commit`, CreateFixupCommitTooltip: `Create fixup commit for this commit`,
SquashAboveCommitsTooltip: `Squash all 'fixup!' commits above selected commit (autosquash)`, SquashAboveCommitsTooltip: `Squash all 'fixup!' commits above selected commit (autosquash)`,
SureSquashAboveCommits: `Are you sure you want to squash all fixup! commits above {{.commit}}?`,
CreateFixupCommit: `Create fixup commit`, CreateFixupCommit: `Create fixup commit`,
SureCreateFixupCommit: `Are you sure you want to create a fixup! commit for commit {{.commit}}?`, SureCreateFixupCommit: `Are you sure you want to create a fixup! commit for commit {{.commit}}?`,
ExecuteCustomCommand: "Execute custom command", ExecuteCustomCommand: "Execute custom command",

View file

@ -180,7 +180,6 @@ func polishTranslationSet() TranslationSet {
ViewResetOptions: "Wyświetl opcje resetu", ViewResetOptions: "Wyświetl opcje resetu",
CreateFixupCommitTooltip: "Utwórz commit naprawczy dla tego commita", CreateFixupCommitTooltip: "Utwórz commit naprawczy dla tego commita",
SquashAboveCommitsTooltip: `Spłaszcz wszystkie commity naprawcze powyżej zaznaczonych commitów (autosquash)`, SquashAboveCommitsTooltip: `Spłaszcz wszystkie commity naprawcze powyżej zaznaczonych commitów (autosquash)`,
SureSquashAboveCommits: `Na pewno chcesz spłaszczyć wszystkie commity naprawcze powyżej {{.commit}}?`,
CreateFixupCommit: `Utwóż commit naprawczy`, CreateFixupCommit: `Utwóż commit naprawczy`,
SureCreateFixupCommit: `Na pewno utworzyć commit naprawczy dla commita {{.commit}}?`, SureCreateFixupCommit: `Na pewno utworzyć commit naprawczy dla commita {{.commit}}?`,
ExecuteCustomCommand: "Wykonaj własną komendę", ExecuteCustomCommand: "Wykonaj własną komendę",

View file

@ -314,7 +314,6 @@ func RussianTranslationSet() TranslationSet {
ViewResetOptions: `Просмотреть параметры сброса`, ViewResetOptions: `Просмотреть параметры сброса`,
CreateFixupCommitTooltip: `Создать fixup коммит для этого коммита`, CreateFixupCommitTooltip: `Создать fixup коммит для этого коммита`,
SquashAboveCommitsTooltip: `Объединить все 'fixup!' коммиты выше в выбранный коммит (автосохранение)`, SquashAboveCommitsTooltip: `Объединить все 'fixup!' коммиты выше в выбранный коммит (автосохранение)`,
SureSquashAboveCommits: `Вы уверены, что хотите объединить все fixup! коммиты выше {{.commit}}?`,
CreateFixupCommit: `Создать fixup коммит`, CreateFixupCommit: `Создать fixup коммит`,
SureCreateFixupCommit: `Вы уверены, что хотите создать fixup! коммит для коммита {{.commit}}?`, SureCreateFixupCommit: `Вы уверены, что хотите создать fixup! коммит для коммита {{.commit}}?`,
ExecuteCustomCommand: "Выполнить пользовательскую команду", ExecuteCustomCommand: "Выполнить пользовательскую команду",

View file

@ -341,7 +341,6 @@ func traditionalChineseTranslationSet() TranslationSet {
ViewResetOptions: "檢視重設選項", ViewResetOptions: "檢視重設選項",
CreateFixupCommitTooltip: "為此提交建立修復提交", CreateFixupCommitTooltip: "為此提交建立修復提交",
SquashAboveCommitsTooltip: "壓縮上方所有的“fixup!”提交 (自動壓縮)", SquashAboveCommitsTooltip: "壓縮上方所有的“fixup!”提交 (自動壓縮)",
SureSquashAboveCommits: "你確定要壓縮{{.commit}}上方所有的fixup!提交嗎?",
CreateFixupCommit: "建立修復提交", CreateFixupCommit: "建立修復提交",
SureCreateFixupCommit: "你確定要為提交{{.commit}}建立fixup!提交嗎?", SureCreateFixupCommit: "你確定要為提交{{.commit}}建立fixup!提交嗎?",
ExecuteCustomCommand: "執行自訂命令", ExecuteCustomCommand: "執行自訂命令",

View file

@ -33,9 +33,9 @@ var SquashFixupsAboveFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{
NavigateToLine(Contains("commit 01").DoesNotContain("fixup!")). NavigateToLine(Contains("commit 01").DoesNotContain("fixup!")).
Press(keys.Commits.SquashAboveCommits). Press(keys.Commits.SquashAboveCommits).
Tap(func() { Tap(func() {
t.ExpectPopup().Confirmation(). t.ExpectPopup().Menu().
Title(Equals("Apply fixup commits")). Title(Equals("Apply fixup commits")).
Content(Contains("Are you sure you want to squash all fixup! commits above")). Select(Contains("Above the selected commit")).
Confirm() Confirm()
}). }).
Lines( Lines(

View file

@ -0,0 +1,55 @@
package interactive_rebase
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var SquashFixupsInCurrentBranch = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Squashes all fixups in the current branch.",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.
CreateFileAndAdd("file1", "file1").
Commit("master commit").
NewBranch("branch").
// Test the pathological case that the first commit of a branch is a
// fixup for the last master commit below it. We _don't_ want this to
// be squashed.
UpdateFileAndAdd("file1", "changed file1").
Commit("fixup! master commit").
CreateNCommits(2).
CreateFileAndAdd("fixup-file", "fixup content").
Commit("fixup! commit 01")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Lines(
Contains("fixup! commit 01"),
Contains("commit 02"),
Contains("commit 01"),
Contains("fixup! master commit"),
Contains("master commit"),
).
Press(keys.Commits.SquashAboveCommits).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Apply fixup commits")).
Select(Contains("In current branch")).
Confirm()
}).
Lines(
Contains("commit 02"),
Contains("commit 01"),
Contains("fixup! master commit"),
Contains("master commit"),
).
NavigateToLine(Contains("commit 01"))
t.Views().Main().
Content(Contains("fixup content"))
},
})

View file

@ -183,6 +183,7 @@ var tests = []*components.IntegrationTest{
interactive_rebase.SquashDownFirstCommit, interactive_rebase.SquashDownFirstCommit,
interactive_rebase.SquashDownSecondCommit, interactive_rebase.SquashDownSecondCommit,
interactive_rebase.SquashFixupsAboveFirstCommit, interactive_rebase.SquashFixupsAboveFirstCommit,
interactive_rebase.SquashFixupsInCurrentBranch,
interactive_rebase.SwapInRebaseWithConflict, interactive_rebase.SwapInRebaseWithConflict,
interactive_rebase.SwapInRebaseWithConflictAndEdit, interactive_rebase.SwapInRebaseWithConflictAndEdit,
interactive_rebase.SwapWithConflict, interactive_rebase.SwapWithConflict,