From b133318b40f9863e9952dddd8acfeffa6dc95b71 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Tue, 23 Jan 2024 17:03:44 +0100 Subject: 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". --- docs/keybindings/Keybindings_en.md | 2 +- docs/keybindings/Keybindings_ja.md | 2 +- pkg/gui/controllers/local_commits_controller.go | 79 +++++++++++++++++----- pkg/i18n/chinese.go | 1 - pkg/i18n/dutch.go | 1 - pkg/i18n/english.go | 14 +++- pkg/i18n/japanese.go | 1 - pkg/i18n/korean.go | 1 - pkg/i18n/polish.go | 1 - pkg/i18n/russian.go | 1 - pkg/i18n/traditional_chinese.go | 1 - .../squash_fixups_above_first_commit.go | 4 +- .../squash_fixups_in_current_branch.go | 55 +++++++++++++++ pkg/integration/tests/test_list.go | 1 + 14 files changed, 133 insertions(+), 31 deletions(-) create mode 100644 pkg/integration/tests/interactive_rebase/squash_fixups_in_current_branch.go diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md index 693c56359..550b3640f 100644 --- a/docs/keybindings/Keybindings_en.md +++ b/docs/keybindings/Keybindings_en.md @@ -89,7 +89,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ 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. | | `` 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). | | `` `` | Move commit down one | | | `` `` | Move commit up one | | | `` V `` | Paste (cherry-pick) | | diff --git a/docs/keybindings/Keybindings_ja.md b/docs/keybindings/Keybindings_ja.md index beb9ee3f4..b19f42fe7 100644 --- a/docs/keybindings/Keybindings_ja.md +++ b/docs/keybindings/Keybindings_ja.md @@ -106,7 +106,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ 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. | | `` 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). | | `` `` | コミットを1つ下に移動 | | | `` `` | コミットを1つ上に移動 | | | `` V `` | コミットを貼り付け (cherry-pick) | | diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go index 3dc1bae7f..3fadbe9e9 100644 --- a/pkg/gui/controllers/local_commits_controller.go +++ b/pkg/gui/controllers/local_commits_controller.go @@ -167,13 +167,13 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [ }, { Key: opts.GetKey(opts.Config.Commits.SquashAboveCommits), - Handler: self.withItem(self.squashAllAboveFixupCommits), + Handler: self.squashFixupCommits, GetDisabledReason: self.require( self.notMidRebase(self.c.Tr.AlreadyRebasing), - self.singleItemSelected(), ), Description: self.c.Tr.SquashAboveCommits, Tooltip: self.c.Tr.SquashAboveCommitsTooltip, + OpensMenu: true, }, { 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 { - prompt := utils.ResolvePlaceholderString( - self.c.Tr.SureSquashAboveCommits, - map[string]string{"commit": commit.Sha}, - ) - - return self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.SquashAboveCommits, - Prompt: prompt, - HandleConfirm: func() 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) squashFixupCommits() error { + return self.c.Menu(types.CreateMenuOptions{ + Title: self.c.Tr.SquashAboveCommits, + Items: []*types.MenuItem{ + { + Label: self.c.Tr.SquashCommitsInCurrentBranch, + OnPress: self.squashAllFixupsInCurrentBranch, + DisabledReason: self.canFindCommitForSquashFixupsInCurrentBranch(), + Key: 'b', + Tooltip: self.c.Tr.SquashCommitsInCurrentBranchTooltip, + }, + { + Label: self.c.Tr.SquashCommitsAboveSelectedCommit, + 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 { return self.c.Helpers().Tags.OpenCreateTagPrompt(commit.Sha, func() {}) } @@ -1019,6 +1056,14 @@ func (self *LocalCommitsController) canFindCommitForQuickStart() *types.Disabled 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 { if endIdx >= len(self.c.Model().Commits)-1 { return &types.DisabledReason{Text: self.c.Tr.CannotSquashOrFixupFirstCommit} diff --git a/pkg/i18n/chinese.go b/pkg/i18n/chinese.go index 6f4440633..90d53995d 100644 --- a/pkg/i18n/chinese.go +++ b/pkg/i18n/chinese.go @@ -254,7 +254,6 @@ func chineseTranslationSet() TranslationSet { ViewResetOptions: `查看重置选项`, CreateFixupCommit: `为此提交创建修正`, SquashAboveCommitsTooltip: `压缩在所选提交之上的所有“fixup!”提交(自动压缩)`, - SureSquashAboveCommits: `您确定要压缩在 {{.commit}} 之上的所有“fixup!”提交吗?`, CreateFixupCommitTooltip: `创建修正提交`, SureCreateFixupCommit: `您确定要对 {{.commit}} 创建修正提交吗?`, ExecuteCustomCommand: "执行自定义命令", diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index 5f7aa2978..7e8a89707 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -217,7 +217,6 @@ func dutchTranslationSet() TranslationSet { HardReset: "Harde reset", CreateFixupCommit: `Creëer fixup commit voor deze commit`, SquashAboveCommitsTooltip: `Squash bovenstaande commits`, - SureSquashAboveCommits: `Weet je zeker dat je alles wil squash/fixup! voor de bovenstaand commits {{.commit}}?`, CreateFixupCommitTooltip: `Creëer fixup commit`, SureCreateFixupCommit: `Weet je zeker dat je een fixup wil maken! commit voor commit {{.commit}}?`, ExecuteCustomCommand: "Voer aangepaste commando uit", diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 7d4c60c26..5ff58d085 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -386,8 +386,12 @@ type TranslationSet struct { CreateFixupCommitDescription string CreateFixupCommitTooltip string SquashAboveCommitsTooltip string + SquashCommitsAboveSelectedTooltip string + SquashCommitsInCurrentBranchTooltip string SquashAboveCommits string - SureSquashAboveCommits string + SquashCommitsInCurrentBranch string + SquashCommitsAboveSelectedCommit string + CannotSquashCommitsInCurrentBranch string SureCreateFixupCommit string ExecuteCustomCommand string ExecuteCustomCommandTooltip string @@ -1322,8 +1326,12 @@ func EnglishTranslationSet() TranslationSet { 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.", SquashAboveCommits: "Apply fixup commits", - SquashAboveCommitsTooltip: `Squash all 'fixup!' commits above selected commit (autosquash).`, - SureSquashAboveCommits: `Are you sure you want to squash all fixup! commits above {{.commit}}?`, + SquashAboveCommitsTooltip: `Squash all 'fixup!' commits, either above the selected commit, or all in current branch (autosquash).`, + 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`, SureCreateFixupCommit: `Are you sure you want to create a fixup! commit for commit {{.commit}}?`, ExecuteCustomCommand: "Execute custom command", diff --git a/pkg/i18n/japanese.go b/pkg/i18n/japanese.go index 19f1a3ca9..f0947f650 100644 --- a/pkg/i18n/japanese.go +++ b/pkg/i18n/japanese.go @@ -263,7 +263,6 @@ func japaneseTranslationSet() TranslationSet { CreateFixupCommitTooltip: `このコミットに対するfixupコミットを作成`, // LcSquashAboveCommits: `squash all 'fixup!' commits above selected commit (autosquash)`, // SquashAboveCommits: `Squash all 'fixup!' commits above selected commit (autosquash)`, - SureSquashAboveCommits: `{{.commit}}に対するすべての fixup! コミットをsquashします。よろしいですか?`, CreateFixupCommit: `Fixupコミットを作成`, SureCreateFixupCommit: `{{.commit}} に対する fixup! コミットを作成します。よろしいですか?`, ExecuteCustomCommand: "カスタムコマンドを実行", diff --git a/pkg/i18n/korean.go b/pkg/i18n/korean.go index 26bb0542e..fdfb96c0a 100644 --- a/pkg/i18n/korean.go +++ b/pkg/i18n/korean.go @@ -258,7 +258,6 @@ func koreanTranslationSet() TranslationSet { ViewResetOptions: `View reset options`, CreateFixupCommitTooltip: `Create fixup commit for this commit`, 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`, SureCreateFixupCommit: `Are you sure you want to create a fixup! commit for commit {{.commit}}?`, ExecuteCustomCommand: "Execute custom command", diff --git a/pkg/i18n/polish.go b/pkg/i18n/polish.go index 2366ff174..fdde08a9c 100644 --- a/pkg/i18n/polish.go +++ b/pkg/i18n/polish.go @@ -180,7 +180,6 @@ func polishTranslationSet() TranslationSet { ViewResetOptions: "Wyświetl opcje resetu", CreateFixupCommitTooltip: "Utwórz commit naprawczy dla tego commita", 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`, SureCreateFixupCommit: `Na pewno utworzyć commit naprawczy dla commita {{.commit}}?`, ExecuteCustomCommand: "Wykonaj własną komendę", diff --git a/pkg/i18n/russian.go b/pkg/i18n/russian.go index b1ac91dbf..af3fdd6bd 100644 --- a/pkg/i18n/russian.go +++ b/pkg/i18n/russian.go @@ -314,7 +314,6 @@ func RussianTranslationSet() TranslationSet { ViewResetOptions: `Просмотреть параметры сброса`, CreateFixupCommitTooltip: `Создать fixup коммит для этого коммита`, SquashAboveCommitsTooltip: `Объединить все 'fixup!' коммиты выше в выбранный коммит (автосохранение)`, - SureSquashAboveCommits: `Вы уверены, что хотите объединить все fixup! коммиты выше {{.commit}}?`, CreateFixupCommit: `Создать fixup коммит`, SureCreateFixupCommit: `Вы уверены, что хотите создать fixup! коммит для коммита {{.commit}}?`, ExecuteCustomCommand: "Выполнить пользовательскую команду", diff --git a/pkg/i18n/traditional_chinese.go b/pkg/i18n/traditional_chinese.go index 76be7b6ef..714c259af 100644 --- a/pkg/i18n/traditional_chinese.go +++ b/pkg/i18n/traditional_chinese.go @@ -341,7 +341,6 @@ func traditionalChineseTranslationSet() TranslationSet { ViewResetOptions: "檢視重設選項", CreateFixupCommitTooltip: "為此提交建立修復提交", SquashAboveCommitsTooltip: "壓縮上方所有的“fixup!”提交 (自動壓縮)", - SureSquashAboveCommits: "你確定要壓縮{{.commit}}上方所有的fixup!提交嗎?", CreateFixupCommit: "建立修復提交", SureCreateFixupCommit: "你確定要為提交{{.commit}}建立fixup!提交嗎?", ExecuteCustomCommand: "執行自訂命令", diff --git a/pkg/integration/tests/interactive_rebase/squash_fixups_above_first_commit.go b/pkg/integration/tests/interactive_rebase/squash_fixups_above_first_commit.go index 94324db77..9445dcf58 100644 --- a/pkg/integration/tests/interactive_rebase/squash_fixups_above_first_commit.go +++ b/pkg/integration/tests/interactive_rebase/squash_fixups_above_first_commit.go @@ -33,9 +33,9 @@ var SquashFixupsAboveFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{ NavigateToLine(Contains("commit 01").DoesNotContain("fixup!")). Press(keys.Commits.SquashAboveCommits). Tap(func() { - t.ExpectPopup().Confirmation(). + t.ExpectPopup().Menu(). 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() }). Lines( diff --git a/pkg/integration/tests/interactive_rebase/squash_fixups_in_current_branch.go b/pkg/integration/tests/interactive_rebase/squash_fixups_in_current_branch.go new file mode 100644 index 000000000..636810533 --- /dev/null +++ b/pkg/integration/tests/interactive_rebase/squash_fixups_in_current_branch.go @@ -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")) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 9c1f8b5fe..cfadde3b1 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -183,6 +183,7 @@ var tests = []*components.IntegrationTest{ interactive_rebase.SquashDownFirstCommit, interactive_rebase.SquashDownSecondCommit, interactive_rebase.SquashFixupsAboveFirstCommit, + interactive_rebase.SquashFixupsInCurrentBranch, interactive_rebase.SwapInRebaseWithConflict, interactive_rebase.SwapInRebaseWithConflictAndEdit, interactive_rebase.SwapWithConflict, -- cgit v1.2.3