From dd61c49a1544e44da4602a7643723e4c6c240648 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 19 Feb 2023 15:08:35 +0100 Subject: [PATCH 1/4] Better error message for trying to squash or fixup the first commit It's not so much the total number of commits that matters here, it's just whether we are on the first one. (This includes the other condition.) This allows us to get rid of the condition in rebase.go. --- pkg/commands/git_commands/rebase.go | 4 -- .../controllers/local_commits_controller.go | 8 ++-- pkg/i18n/chinese.go | 3 +- pkg/i18n/dutch.go | 3 +- pkg/i18n/english.go | 6 +-- pkg/i18n/japanese.go | 3 +- pkg/i18n/korean.go | 3 +- pkg/i18n/polish.go | 3 +- .../interactive_rebase/fixup_first_commit.go | 37 +++++++++++++++++++ .../squash_down_first_commit.go | 37 +++++++++++++++++++ pkg/integration/tests/tests_gen.go | 2 + 11 files changed, 87 insertions(+), 22 deletions(-) create mode 100644 pkg/integration/tests/interactive_rebase/fixup_first_commit.go create mode 100644 pkg/integration/tests/interactive_rebase/squash_down_first_commit.go diff --git a/pkg/commands/git_commands/rebase.go b/pkg/commands/git_commands/rebase.go index 66dbcf45f..6990d6bd5 100644 --- a/pkg/commands/git_commands/rebase.go +++ b/pkg/commands/git_commands/rebase.go @@ -178,10 +178,6 @@ func (self *RebaseCommands) BuildSingleActionTodo(commits []*models.Commit, acti if action == "squash" || action == "fixup" { baseIndex++ - - if len(commits) <= baseIndex { - return nil, "", errors.New(self.Tr.CannotSquashOntoSecondCommit) - } } todoLines := self.BuildTodoLines(commits[0:baseIndex], func(commit *models.Commit, i int) string { diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go index a9fe7a190..42920bf81 100644 --- a/pkg/gui/controllers/local_commits_controller.go +++ b/pkg/gui/controllers/local_commits_controller.go @@ -148,8 +148,8 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [ } func (self *LocalCommitsController) squashDown(commit *models.Commit) error { - if len(self.model.Commits) <= 1 { - return self.c.ErrorMsg(self.c.Tr.YouNoCommitsToSquash) + if self.context().GetSelectedLineIdx() >= len(self.model.Commits)-1 { + return self.c.ErrorMsg(self.c.Tr.CannotSquashOrFixupFirstCommit) } applied, err := self.handleMidRebaseCommand("squash", commit) @@ -173,8 +173,8 @@ func (self *LocalCommitsController) squashDown(commit *models.Commit) error { } func (self *LocalCommitsController) fixup(commit *models.Commit) error { - if len(self.model.Commits) <= 1 { - return self.c.ErrorMsg(self.c.Tr.YouNoCommitsToSquash) + if self.context().GetSelectedLineIdx() >= len(self.model.Commits)-1 { + return self.c.ErrorMsg(self.c.Tr.CannotSquashOrFixupFirstCommit) } applied, err := self.handleMidRebaseCommand("fixup", commit) diff --git a/pkg/i18n/chinese.go b/pkg/i18n/chinese.go index 9533f0239..68ee2735b 100644 --- a/pkg/i18n/chinese.go +++ b/pkg/i18n/chinese.go @@ -102,7 +102,7 @@ func chineseTranslationSet() TranslationSet { LcSquashDown: "向下压缩", LcFixupCommit: "修正提交(fixup)", NoCommitsThisBranch: "该分支没有提交", - YouNoCommitsToSquash: "您没有提交可以压缩", + CannotSquashOrFixupFirstCommit: "There's no commit below to squash into", Fixup: "修正(fixup)", SureFixupThisCommit: "您确定要“修正”此提交吗?它将合并到下面的提交中", SureSquashThisCommit: "您确定要将这个提交压缩到下面的提交中吗?", @@ -217,7 +217,6 @@ func chineseTranslationSet() TranslationSet { SureCherryPick: "您确定要将选中的提交进行拣选到这个分支吗?", CherryPick: "拣选 (Cherry-Pick)", CannotRebaseOntoFirstCommit: "您不能以交互方式变基 (rebase) 至第一次提交", - CannotSquashOntoSecondCommit: "您不能压缩 (squash) 或修正 (fixup) 第二个提交", Donate: "捐助", AskQuestion: "提问咨询", PrevLine: "选择上一行", diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index 0127c07ad..31ef939ab 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -67,7 +67,7 @@ func dutchTranslationSet() TranslationSet { LcQuit: "quit", LcSquashDown: "squash beneden", LcFixupCommit: "Fixup commit", - YouNoCommitsToSquash: "Je hebt geen commits om mee te squashen", + CannotSquashOrFixupFirstCommit: "There's no commit below to squash into", Fixup: "Fixup", SureFixupThisCommit: "Weet je zeker dat je fixup wil uitvoeren op deze commit? De commit hieronder zol worden squashed in deze", SureSquashThisCommit: "Weet je zeker dat je deze commit wil samenvoegen met de commit hieronder?", @@ -182,7 +182,6 @@ func dutchTranslationSet() TranslationSet { SureCherryPick: "Weet je zeker dat je de gekopieerde commits naar deze branch wil cherry-picken?", CherryPick: "Cherry-Pick", CannotRebaseOntoFirstCommit: "Je kan niet interactief rebasen naar de eerste commit", - CannotSquashOntoSecondCommit: "Je kan niet een squash/fixup doen naar de 2de commit", Donate: "Doneer", PrevLine: "selecteer de vorige lijn", NextLine: "selecteer de volgende lijn", diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 06ffd9b1c..6639b9e48 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -87,7 +87,7 @@ type TranslationSet struct { LcQuit string LcSquashDown string LcFixupCommit string - YouNoCommitsToSquash string + CannotSquashOrFixupFirstCommit string Fixup string SureFixupThisCommit string SureSquashThisCommit string @@ -220,7 +220,6 @@ type TranslationSet struct { SureCherryPick string CherryPick string CannotRebaseOntoFirstCommit string - CannotSquashOntoSecondCommit string Donate string AskQuestion string PrevLine string @@ -737,7 +736,7 @@ func EnglishTranslationSet() TranslationSet { LcSquashDown: "squash down", LcFixupCommit: "fixup commit", NoCommitsThisBranch: "No commits for this branch", - YouNoCommitsToSquash: "You have no commits to squash with", + 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?", @@ -870,7 +869,6 @@ func EnglishTranslationSet() TranslationSet { SureCherryPick: "Are you sure you want to cherry-pick the copied commits onto this branch?", CherryPick: "Cherry-Pick", CannotRebaseOntoFirstCommit: "You cannot interactive rebase onto the first commit", - CannotSquashOntoSecondCommit: "You cannot squash/fixup onto the second commit", Donate: "Donate", AskQuestion: "Ask Question", PrevLine: "select previous line", diff --git a/pkg/i18n/japanese.go b/pkg/i18n/japanese.go index 5248287a9..122fd3133 100644 --- a/pkg/i18n/japanese.go +++ b/pkg/i18n/japanese.go @@ -93,7 +93,7 @@ func japaneseTranslationSet() TranslationSet { // LcSquashDown: "squash down", // LcFixupCommit: "fixup commit", // NoCommitsThisBranch: "No commits for this branch", - // YouNoCommitsToSquash: "You have no commits to squash with", + // 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?", @@ -217,7 +217,6 @@ func japaneseTranslationSet() TranslationSet { // SureCherryPick: "Are you sure you want to cherry-pick the copied commits onto this branch?", CherryPick: "Cherry-Pick", // CannotRebaseOntoFirstCommit: "You cannot interactive rebase onto the first commit", - // CannotSquashOntoSecondCommit: "You cannot squash/fixup onto the second commit", Donate: "支援", AskQuestion: "質問", PrevLine: "前の行を選択", diff --git a/pkg/i18n/korean.go b/pkg/i18n/korean.go index c191a436e..361df81f2 100644 --- a/pkg/i18n/korean.go +++ b/pkg/i18n/korean.go @@ -92,7 +92,7 @@ func koreanTranslationSet() TranslationSet { LcSquashDown: "squash down", LcFixupCommit: "fixup commit", NoCommitsThisBranch: "이 브랜치에 커밋이 없습니다.", - YouNoCommitsToSquash: "You have no commits to squash with", + 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?", @@ -218,7 +218,6 @@ func koreanTranslationSet() TranslationSet { SureCherryPick: "정말로 복사한 커밋을 이 브랜치에 체리픽하시겠습니까?", CherryPick: "체리픽", CannotRebaseOntoFirstCommit: "첫 번째 커밋에 대해 대화식으로 리베이스할 수 없습니다.", - CannotSquashOntoSecondCommit: "두 번째 커밋을 squash/fixup할 수 없습니다.", Donate: "후원", AskQuestion: "질문하기", PrevLine: "이전 줄 선택", diff --git a/pkg/i18n/polish.go b/pkg/i18n/polish.go index 05534c365..5bc2a9a35 100644 --- a/pkg/i18n/polish.go +++ b/pkg/i18n/polish.go @@ -62,7 +62,7 @@ func polishTranslationSet() TranslationSet { LcSquashDown: "ściśnij", LcFixupCommit: "napraw commit", NoCommitsThisBranch: "Brak commitów dla tej gałęzi", - YouNoCommitsToSquash: "Nie masz commitów do spłaszczenia", + CannotSquashOrFixupFirstCommit: "There's no commit below to squash into", Fixup: "Napraw", SureFixupThisCommit: "Jesteś pewny, ze chcesz naprawić ten commit? Commit poniżej zostanie spłaszczony w górę wraz z tym", LcRewordCommit: "zmień nazwę commita", @@ -150,7 +150,6 @@ func polishTranslationSet() TranslationSet { SureCherryPick: "Czy na pewno chcesz przebierać w skopiowanych commitach na tej gałęzi?", CherryPick: "Przebieranie", CannotRebaseOntoFirstCommit: "Nie można interaktywnie zmienić bazy na pierwszym commicie", - CannotSquashOntoSecondCommit: "Nie można spłaszczyć na drugi commit", Donate: "Wesprzyj", PrevLine: "poprzednia linia", NextLine: "następna linia", diff --git a/pkg/integration/tests/interactive_rebase/fixup_first_commit.go b/pkg/integration/tests/interactive_rebase/fixup_first_commit.go new file mode 100644 index 000000000..fe339f3e2 --- /dev/null +++ b/pkg/integration/tests/interactive_rebase/fixup_first_commit.go @@ -0,0 +1,37 @@ +package interactive_rebase + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var FixupFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Tries to fixup the first commit, which results in an error message", + ExtraCmdArgs: "", + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell. + CreateNCommits(2) + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("commit 02"), + Contains("commit 01"), + ). + NavigateToListItem(Contains("commit 01")). + Press(keys.Commits.MarkCommitAsFixup). + Tap(func() { + t.ExpectPopup().Alert(). + Title(Equals("Error")). + Content(Equals("There's no commit below to squash into")). + Confirm() + }). + Lines( + Contains("commit 02"), + Contains("commit 01"), + ) + }, +}) diff --git a/pkg/integration/tests/interactive_rebase/squash_down_first_commit.go b/pkg/integration/tests/interactive_rebase/squash_down_first_commit.go new file mode 100644 index 000000000..0ec334a6c --- /dev/null +++ b/pkg/integration/tests/interactive_rebase/squash_down_first_commit.go @@ -0,0 +1,37 @@ +package interactive_rebase + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var SquashDownFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Tries to squash down the first commit, which results in an error message", + ExtraCmdArgs: "", + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell. + CreateNCommits(2) + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("commit 02"), + Contains("commit 01"), + ). + NavigateToListItem(Contains("commit 01")). + Press(keys.Commits.SquashDown). + Tap(func() { + t.ExpectPopup().Alert(). + Title(Equals("Error")). + Content(Equals("There's no commit below to squash into")). + Confirm() + }). + Lines( + Contains("commit 02"), + Contains("commit 01"), + ) + }, +}) diff --git a/pkg/integration/tests/tests_gen.go b/pkg/integration/tests/tests_gen.go index 2be0b902c..b711ea6d7 100644 --- a/pkg/integration/tests/tests_gen.go +++ b/pkg/integration/tests/tests_gen.go @@ -66,7 +66,9 @@ var tests = []*components.IntegrationTest{ filter_by_path.SelectFile, filter_by_path.TypeFile, interactive_rebase.AmendMerge, + interactive_rebase.FixupFirstCommit, interactive_rebase.One, + interactive_rebase.SquashDownFirstCommit, misc.ConfirmOnQuit, misc.InitialOpen, patch_building.CopyPatchToClipboard, From a349e886ce1c39dd2e279b83eaaefed523df9fa7 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 19 Feb 2023 15:20:00 +0100 Subject: [PATCH 2/4] Allow interactive rebasing all the way down to the first commit Pass --root instead of a sha when we want to rebase down to the initial commit. --- pkg/commands/git_commands/rebase.go | 15 ++++++++------- pkg/i18n/chinese.go | 1 - pkg/i18n/dutch.go | 1 - pkg/i18n/english.go | 2 -- pkg/i18n/japanese.go | 3 +-- pkg/i18n/korean.go | 1 - pkg/i18n/polish.go | 1 - 7 files changed, 9 insertions(+), 15 deletions(-) diff --git a/pkg/commands/git_commands/rebase.go b/pkg/commands/git_commands/rebase.go index 6990d6bd5..0c79e92b7 100644 --- a/pkg/commands/git_commands/rebase.go +++ b/pkg/commands/git_commands/rebase.go @@ -130,7 +130,7 @@ func (self *RebaseCommands) InteractiveRebaseBreakAfter(commits []*models.Commit // 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, todoLines []TodoLine, overrideEditor bool) oscommands.ICmdObj { +func (self *RebaseCommands) PrepareInteractiveRebaseCommand(baseShaOrRoot string, todoLines []TodoLine, overrideEditor bool) oscommands.ICmdObj { todo := self.buildTodo(todoLines) ex := oscommands.GetLazygitPath() @@ -139,7 +139,7 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(baseSha string, todo debug = "TRUE" } - cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty --no-autosquash %s", baseSha) + cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty --no-autosquash %s", baseShaOrRoot) self.Log.WithField("command", cmdStr).Debug("RunCommand") cmdObj := self.cmd.New(cmdStr) @@ -172,10 +172,6 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(baseSha string, todo func (self *RebaseCommands) BuildSingleActionTodo(commits []*models.Commit, actionIndex int, action string) ([]TodoLine, string, error) { baseIndex := actionIndex + 1 - if len(commits) <= baseIndex { - return nil, "", errors.New(self.Tr.CannotRebaseOntoFirstCommit) - } - if action == "squash" || action == "fixup" { baseIndex++ } @@ -193,7 +189,12 @@ func (self *RebaseCommands) BuildSingleActionTodo(commits []*models.Commit, acti } }) - return todoLines, commits[baseIndex].Sha, nil + baseSha := "--root" + if baseIndex < len(commits) { + baseSha = commits[baseIndex].Sha + } + + return todoLines, baseSha, nil } // AmendTo amends the given commit with whatever files are staged diff --git a/pkg/i18n/chinese.go b/pkg/i18n/chinese.go index 68ee2735b..dbc8122e9 100644 --- a/pkg/i18n/chinese.go +++ b/pkg/i18n/chinese.go @@ -216,7 +216,6 @@ func chineseTranslationSet() TranslationSet { LcPasteCommits: "粘贴提交(拣选)", SureCherryPick: "您确定要将选中的提交进行拣选到这个分支吗?", CherryPick: "拣选 (Cherry-Pick)", - CannotRebaseOntoFirstCommit: "您不能以交互方式变基 (rebase) 至第一次提交", Donate: "捐助", AskQuestion: "提问咨询", PrevLine: "选择上一行", diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index 31ef939ab..d8fcb5338 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -181,7 +181,6 @@ func dutchTranslationSet() TranslationSet { LcPasteCommits: "plak commits (cherry-pick)", SureCherryPick: "Weet je zeker dat je de gekopieerde commits naar deze branch wil cherry-picken?", CherryPick: "Cherry-Pick", - CannotRebaseOntoFirstCommit: "Je kan niet interactief rebasen naar de eerste commit", Donate: "Doneer", PrevLine: "selecteer de vorige lijn", NextLine: "selecteer de volgende lijn", diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 6639b9e48..315851f35 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -219,7 +219,6 @@ type TranslationSet struct { LcPasteCommits string SureCherryPick string CherryPick string - CannotRebaseOntoFirstCommit string Donate string AskQuestion string PrevLine string @@ -868,7 +867,6 @@ func EnglishTranslationSet() TranslationSet { LcPasteCommits: "paste commits (cherry-pick)", SureCherryPick: "Are you sure you want to cherry-pick the copied commits onto this branch?", CherryPick: "Cherry-Pick", - CannotRebaseOntoFirstCommit: "You cannot interactive rebase onto the first commit", Donate: "Donate", AskQuestion: "Ask Question", PrevLine: "select previous line", diff --git a/pkg/i18n/japanese.go b/pkg/i18n/japanese.go index 122fd3133..891d737b0 100644 --- a/pkg/i18n/japanese.go +++ b/pkg/i18n/japanese.go @@ -215,8 +215,7 @@ func japaneseTranslationSet() TranslationSet { LcCherryPickCopyRange: "コミットを範囲コピー (cherry-pick)", LcPasteCommits: "コミットを貼り付け (cherry-pick)", // SureCherryPick: "Are you sure you want to cherry-pick the copied commits onto this branch?", - CherryPick: "Cherry-Pick", - // CannotRebaseOntoFirstCommit: "You cannot interactive rebase onto the first commit", + CherryPick: "Cherry-Pick", Donate: "支援", AskQuestion: "質問", PrevLine: "前の行を選択", diff --git a/pkg/i18n/korean.go b/pkg/i18n/korean.go index 361df81f2..fa649bcc0 100644 --- a/pkg/i18n/korean.go +++ b/pkg/i18n/korean.go @@ -217,7 +217,6 @@ func koreanTranslationSet() TranslationSet { LcPasteCommits: "커밋을 붙여넣기 (cherry-pick)", SureCherryPick: "정말로 복사한 커밋을 이 브랜치에 체리픽하시겠습니까?", CherryPick: "체리픽", - CannotRebaseOntoFirstCommit: "첫 번째 커밋에 대해 대화식으로 리베이스할 수 없습니다.", Donate: "후원", AskQuestion: "질문하기", PrevLine: "이전 줄 선택", diff --git a/pkg/i18n/polish.go b/pkg/i18n/polish.go index 5bc2a9a35..3db26a22c 100644 --- a/pkg/i18n/polish.go +++ b/pkg/i18n/polish.go @@ -149,7 +149,6 @@ func polishTranslationSet() TranslationSet { LcPasteCommits: "wklej commity (przebieranie)", SureCherryPick: "Czy na pewno chcesz przebierać w skopiowanych commitach na tej gałęzi?", CherryPick: "Przebieranie", - CannotRebaseOntoFirstCommit: "Nie można interaktywnie zmienić bazy na pierwszym commicie", Donate: "Wesprzyj", PrevLine: "poprzednia linia", NextLine: "następna linia", From 7351907474715c2bf86c6d52ef87b9d6d96d8f53 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 19 Feb 2023 11:33:58 +0100 Subject: [PATCH 3/4] Add integration tests for rebasing to the initial commit --- .../interactive_rebase/edit_first_commit.go | 38 ++++++++++++++++ .../interactive_rebase/fixup_second_commit.go | 44 +++++++++++++++++++ .../interactive_rebase/reword_first_commit.go | 39 ++++++++++++++++ .../squash_down_second_commit.go | 43 ++++++++++++++++++ pkg/integration/tests/tests_gen.go | 4 ++ 5 files changed, 168 insertions(+) create mode 100644 pkg/integration/tests/interactive_rebase/edit_first_commit.go create mode 100644 pkg/integration/tests/interactive_rebase/fixup_second_commit.go create mode 100644 pkg/integration/tests/interactive_rebase/reword_first_commit.go create mode 100644 pkg/integration/tests/interactive_rebase/squash_down_second_commit.go diff --git a/pkg/integration/tests/interactive_rebase/edit_first_commit.go b/pkg/integration/tests/interactive_rebase/edit_first_commit.go new file mode 100644 index 000000000..def3b7fca --- /dev/null +++ b/pkg/integration/tests/interactive_rebase/edit_first_commit.go @@ -0,0 +1,38 @@ +package interactive_rebase + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var EditFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Edits the first commit, just to show that it's possible", + ExtraCmdArgs: "", + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell. + CreateNCommits(2) + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("commit 02"), + Contains("commit 01"), + ). + NavigateToListItem(Contains("commit 01")). + Press(keys.Universal.Edit). + Lines( + Contains("commit 02"), + MatchesRegexp("YOU ARE HERE.*commit 01").IsSelected(), + ). + Tap(func() { + t.Actions().ContinueRebase() + }). + Lines( + Contains("commit 02"), + Contains("commit 01"), + ) + }, +}) diff --git a/pkg/integration/tests/interactive_rebase/fixup_second_commit.go b/pkg/integration/tests/interactive_rebase/fixup_second_commit.go new file mode 100644 index 000000000..007eba84f --- /dev/null +++ b/pkg/integration/tests/interactive_rebase/fixup_second_commit.go @@ -0,0 +1,44 @@ +package interactive_rebase + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var FixupSecondCommit = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Fixup the second commit into the first (initial)", + ExtraCmdArgs: "", + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell. + CreateNCommits(3) + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("commit 03"), + Contains("commit 02"), + Contains("commit 01"), + ). + NavigateToListItem(Contains("commit 02")). + Press(keys.Commits.MarkCommitAsFixup). + 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")). + Confirm() + }). + Lines( + Contains("commit 03"), + Contains("commit 01").IsSelected(), + ) + + t.Views().Main(). + Content(Contains("commit 01")). + Content(DoesNotContain("commit 02")). + Content(Contains("+file01 content")). + Content(Contains("+file02 content")) + }, +}) diff --git a/pkg/integration/tests/interactive_rebase/reword_first_commit.go b/pkg/integration/tests/interactive_rebase/reword_first_commit.go new file mode 100644 index 000000000..9e631da09 --- /dev/null +++ b/pkg/integration/tests/interactive_rebase/reword_first_commit.go @@ -0,0 +1,39 @@ +package interactive_rebase + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var RewordFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Rewords the first commit, just to show that it's possible", + ExtraCmdArgs: "", + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell. + CreateNCommits(2) + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("commit 02"), + Contains("commit 01"), + ). + NavigateToListItem(Contains("commit 01")). + Press(keys.Commits.RenameCommit). + Tap(func() { + t.ExpectPopup().Prompt(). + Title(Equals("reword commit")). + InitialText(Equals("commit 01")). + Clear(). + Type("renamed 01"). + Confirm() + }). + Lines( + Contains("commit 02"), + Contains("renamed 01"), + ) + }, +}) diff --git a/pkg/integration/tests/interactive_rebase/squash_down_second_commit.go b/pkg/integration/tests/interactive_rebase/squash_down_second_commit.go new file mode 100644 index 000000000..ffa54584f --- /dev/null +++ b/pkg/integration/tests/interactive_rebase/squash_down_second_commit.go @@ -0,0 +1,43 @@ +package interactive_rebase + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var SquashDownSecondCommit = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Squash down the second commit into the first (initial)", + ExtraCmdArgs: "", + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell. + CreateNCommits(3) + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("commit 03"), + Contains("commit 02"), + Contains("commit 01"), + ). + NavigateToListItem(Contains("commit 02")). + Press(keys.Commits.SquashDown). + Tap(func() { + t.ExpectPopup().Confirmation(). + Title(Equals("Squash")). + Content(Equals("Are you sure you want to squash this commit into the commit below?")). + Confirm() + }). + Lines( + Contains("commit 03"), + Contains("commit 01").IsSelected(), + ) + + t.Views().Main(). + Content(Contains(" commit 01\n \n commit 02")). + Content(Contains("+file01 content")). + Content(Contains("+file02 content")) + }, +}) diff --git a/pkg/integration/tests/tests_gen.go b/pkg/integration/tests/tests_gen.go index b711ea6d7..0e1b3b01c 100644 --- a/pkg/integration/tests/tests_gen.go +++ b/pkg/integration/tests/tests_gen.go @@ -66,9 +66,13 @@ var tests = []*components.IntegrationTest{ filter_by_path.SelectFile, filter_by_path.TypeFile, interactive_rebase.AmendMerge, + interactive_rebase.EditFirstCommit, interactive_rebase.FixupFirstCommit, + interactive_rebase.FixupSecondCommit, interactive_rebase.One, + interactive_rebase.RewordFirstCommit, interactive_rebase.SquashDownFirstCommit, + interactive_rebase.SquashDownSecondCommit, misc.ConfirmOnQuit, misc.InitialOpen, patch_building.CopyPatchToClipboard, From c5cd217a6504259c4040402f6044b1d50ca3a410 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Mon, 20 Feb 2023 08:29:43 +0100 Subject: [PATCH 4/4] Allow squashing fixups above the first commit of a repo This includes amending changes into a given commit, since that's implemented in terms of the former. --- pkg/commands/git_commands/rebase.go | 17 ++++--- .../controllers/local_commits_controller.go | 4 +- .../interactive_rebase/amend_first_commit.go | 41 ++++++++++++++++ .../squash_fixups_above_first_commit.go | 49 +++++++++++++++++++ pkg/integration/tests/tests_gen.go | 2 + 5 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 pkg/integration/tests/interactive_rebase/amend_first_commit.go create mode 100644 pkg/integration/tests/interactive_rebase/squash_fixups_above_first_commit.go diff --git a/pkg/commands/git_commands/rebase.go b/pkg/commands/git_commands/rebase.go index 0c79e92b7..50ab4de7a 100644 --- a/pkg/commands/git_commands/rebase.go +++ b/pkg/commands/git_commands/rebase.go @@ -198,12 +198,12 @@ func (self *RebaseCommands) BuildSingleActionTodo(commits []*models.Commit, acti } // AmendTo amends the given commit with whatever files are staged -func (self *RebaseCommands) AmendTo(sha string) error { - if err := self.commit.CreateFixupCommit(sha); err != nil { +func (self *RebaseCommands) AmendTo(commit *models.Commit) error { + if err := self.commit.CreateFixupCommit(commit.Sha); err != nil { return err } - return self.SquashAllAboveFixupCommits(sha) + return self.SquashAllAboveFixupCommits(commit) } // EditRebaseTodo sets the action at a given index in the git-rebase-todo file @@ -258,12 +258,17 @@ func (self *RebaseCommands) MoveTodoDown(index int) error { } // SquashAllAboveFixupCommits squashes all fixup! commits above the given one -func (self *RebaseCommands) SquashAllAboveFixupCommits(sha string) error { +func (self *RebaseCommands) SquashAllAboveFixupCommits(commit *models.Commit) error { + shaOrRoot := commit.Sha + "^" + if commit.IsFirstCommit() { + shaOrRoot = "--root" + } + return self.runSkipEditorCommand( self.cmd.New( fmt.Sprintf( - "git rebase --interactive --rebase-merges --autostash --autosquash %s^", - sha, + "git rebase --interactive --rebase-merges --autostash --autosquash %s", + shaOrRoot, ), ), ) diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go index 42920bf81..0cee212d1 100644 --- a/pkg/gui/controllers/local_commits_controller.go +++ b/pkg/gui/controllers/local_commits_controller.go @@ -428,7 +428,7 @@ func (self *LocalCommitsController) amendTo(commit *models.Commit) error { HandleConfirm: func() error { return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func() error { self.c.LogAction(self.c.Tr.Actions.AmendCommit) - err := self.git.Rebase.AmendTo(commit.Sha) + err := self.git.Rebase.AmendTo(commit) return self.helpers.MergeAndRebase.CheckMergeOrRebase(err) }) }, @@ -571,7 +571,7 @@ func (self *LocalCommitsController) squashAllAboveFixupCommits(commit *models.Co HandleConfirm: func() error { return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func() error { self.c.LogAction(self.c.Tr.Actions.SquashAllAboveFixupCommits) - err := self.git.Rebase.SquashAllAboveFixupCommits(commit.Sha) + err := self.git.Rebase.SquashAllAboveFixupCommits(commit) return self.helpers.MergeAndRebase.CheckMergeOrRebase(err) }) }, diff --git a/pkg/integration/tests/interactive_rebase/amend_first_commit.go b/pkg/integration/tests/interactive_rebase/amend_first_commit.go new file mode 100644 index 000000000..d03a880b9 --- /dev/null +++ b/pkg/integration/tests/interactive_rebase/amend_first_commit.go @@ -0,0 +1,41 @@ +package interactive_rebase + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var AmendFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Amends a staged file to the first (initial) commit.", + ExtraCmdArgs: "", + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell. + CreateNCommits(2). + CreateFileAndAdd("fixup-file", "fixup content") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("commit 02"), + Contains("commit 01"), + ). + NavigateToListItem(Contains("commit 01")). + Press(keys.Commits.AmendToCommit). + Tap(func() { + t.ExpectPopup().Confirmation(). + Title(Equals("Amend Commit")). + Content(Contains("Are you sure you want to amend this commit with your staged files?")). + Confirm() + }). + Lines( + Contains("commit 02"), + Contains("commit 01").IsSelected(), + ) + + t.Views().Main(). + Content(Contains("fixup content")) + }, +}) 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 new file mode 100644 index 000000000..ece1e5fae --- /dev/null +++ b/pkg/integration/tests/interactive_rebase/squash_fixups_above_first_commit.go @@ -0,0 +1,49 @@ +package interactive_rebase + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var SquashFixupsAboveFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Squashes all fixups above the first (initial) commit.", + ExtraCmdArgs: "", + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell. + CreateNCommits(2). + CreateFileAndAdd("fixup-file", "fixup content") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("commit 02"), + Contains("commit 01"), + ). + NavigateToListItem(Contains("commit 01")). + Press(keys.Commits.CreateFixupCommit). + Tap(func() { + t.ExpectPopup().Confirmation(). + Title(Equals("Create fixup commit")). + Content(Contains("Are you sure you want to create a fixup! commit for commit")). + Confirm() + }). + NavigateToListItem(Contains("commit 01")). + Press(keys.Commits.SquashAboveCommits). + Tap(func() { + t.ExpectPopup().Confirmation(). + Title(Equals("Squash all 'fixup!' commits above selected commit (autosquash)")). + Content(Contains("Are you sure you want to squash all fixup! commits above")). + Confirm() + }). + Lines( + Contains("commit 02"), + Contains("commit 01").IsSelected(), + ) + + t.Views().Main(). + Content(Contains("fixup content")) + }, +}) diff --git a/pkg/integration/tests/tests_gen.go b/pkg/integration/tests/tests_gen.go index 0e1b3b01c..3bef44c7e 100644 --- a/pkg/integration/tests/tests_gen.go +++ b/pkg/integration/tests/tests_gen.go @@ -65,6 +65,7 @@ var tests = []*components.IntegrationTest{ filter_by_path.CliArg, filter_by_path.SelectFile, filter_by_path.TypeFile, + interactive_rebase.AmendFirstCommit, interactive_rebase.AmendMerge, interactive_rebase.EditFirstCommit, interactive_rebase.FixupFirstCommit, @@ -73,6 +74,7 @@ var tests = []*components.IntegrationTest{ interactive_rebase.RewordFirstCommit, interactive_rebase.SquashDownFirstCommit, interactive_rebase.SquashDownSecondCommit, + interactive_rebase.SquashFixupsAboveFirstCommit, misc.ConfirmOnQuit, misc.InitialOpen, patch_building.CopyPatchToClipboard,