From 368f9c8cb3d1f86d2a854cdfc20a8c87221e4087 Mon Sep 17 00:00:00 2001 From: AzraelSec Date: Wed, 29 Mar 2023 00:49:19 +0200 Subject: [PATCH 1/9] feat: let interactive rebase prepend commands to the default todo file --- pkg/app/daemon/daemon.go | 26 ++++++++++++++++++++++---- pkg/commands/git_commands/patch.go | 2 +- pkg/commands/git_commands/rebase.go | 21 +++++++++++++-------- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/pkg/app/daemon/daemon.go b/pkg/app/daemon/daemon.go index 182807b02..5490a25e5 100644 --- a/pkg/app/daemon/daemon.go +++ b/pkg/app/daemon/daemon.go @@ -29,6 +29,11 @@ const ( const ( DaemonKindEnvKey string = "LAZYGIT_DAEMON_KIND" RebaseTODOEnvKey string = "LAZYGIT_REBASE_TODO" + + // The `PrependLinesEnvKey` env variable is set to `true` to tell our daemon + // to prepend the content of `RebaseTODOEnvKey` to the default `git-rebase-todo` + // file instead of using it as a replacement. + PrependLinesEnvKey string = "LAZYGIT_PREPEND_LINES" ) type Daemon interface { @@ -74,12 +79,25 @@ type rebaseDaemon struct { func (self *rebaseDaemon) Run() error { self.c.Log.Info("Lazygit invoked as interactive rebase demon") self.c.Log.Info("args: ", os.Args) + filePath := os.Args[1] - if strings.HasSuffix(os.Args[1], "git-rebase-todo") { - if err := os.WriteFile(os.Args[1], []byte(os.Getenv(RebaseTODOEnvKey)), 0o644); err != nil { - return err + if strings.HasSuffix(filePath, "git-rebase-todo") { + todoEnvKey := os.Getenv(RebaseTODOEnvKey) + + var todoContent []byte + if v := os.Getenv(PrependLinesEnvKey); v != "" { + existingContent, err := os.ReadFile(filePath) + if err != nil { + return err + } + + todoContent = append([]byte(todoEnvKey), existingContent...) + } else { + todoContent = []byte(todoEnvKey) } - } else if strings.HasSuffix(os.Args[1], filepath.Join(gitDir(), "COMMIT_EDITMSG")) { // TODO: test + + return os.WriteFile(filePath, todoContent, 0o644) + } else if strings.HasSuffix(filePath, filepath.Join(gitDir(), "COMMIT_EDITMSG")) { // TODO: test // if we are rebasing and squashing, we'll see a COMMIT_EDITMSG // but in this case we don't need to edit it, so we'll just return } else { diff --git a/pkg/commands/git_commands/patch.go b/pkg/commands/git_commands/patch.go index 7511d1a5b..b5d7bf313 100644 --- a/pkg/commands/git_commands/patch.go +++ b/pkg/commands/git_commands/patch.go @@ -111,7 +111,7 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s } }) - err := self.rebase.PrepareInteractiveRebaseCommand(commits[baseIndex].Sha, todoLines, true).Run() + err := self.rebase.PrepareInteractiveRebaseCommand(commits[baseIndex].Sha, todoLines, true, false).Run() if err != nil { return err } diff --git a/pkg/commands/git_commands/rebase.go b/pkg/commands/git_commands/rebase.go index e4c20426f..4e4892d8d 100644 --- a/pkg/commands/git_commands/rebase.go +++ b/pkg/commands/git_commands/rebase.go @@ -60,7 +60,7 @@ func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index return nil, err } - return self.PrepareInteractiveRebaseCommand(sha, todo, false), nil + return self.PrepareInteractiveRebaseCommand(sha, todo, false, false), nil } func (self *RebaseCommands) ResetCommitAuthor(commits []*models.Commit, index int) error { @@ -104,7 +104,7 @@ func (self *RebaseCommands) MoveCommitDown(commits []*models.Commit, index int) baseShaOrRoot := getBaseShaOrRoot(commits, index+2) - return self.PrepareInteractiveRebaseCommand(baseShaOrRoot, todoLines, true).Run() + return self.PrepareInteractiveRebaseCommand(baseShaOrRoot, todoLines, true, false).Run() } func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index int, action string) error { @@ -113,7 +113,7 @@ func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index in return err } - return self.PrepareInteractiveRebaseCommand(sha, todo, true).Run() + return self.PrepareInteractiveRebaseCommand(sha, todo, true, false).Run() } func (self *RebaseCommands) InteractiveRebaseBreakAfter(commits []*models.Commit, index int) error { @@ -123,15 +123,19 @@ func (self *RebaseCommands) InteractiveRebaseBreakAfter(commits []*models.Commit } todo = append(todo, TodoLine{Action: "break", Commit: nil}) - return self.PrepareInteractiveRebaseCommand(sha, todo, true).Run() + return self.PrepareInteractiveRebaseCommand(sha, todo, true, false).Run() } // 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(baseShaOrRoot string, todoLines []TodoLine, overrideEditor bool) oscommands.ICmdObj { +func (self *RebaseCommands) PrepareInteractiveRebaseCommand(baseShaOrRoot string, todoLines []TodoLine, overrideEditor bool, prepend bool) oscommands.ICmdObj { todo := self.buildTodo(todoLines) ex := oscommands.GetLazygitPath() + prependLines := "" + if prepend { + prependLines = "TRUE" + } debug := "FALSE" if self.Debug { @@ -153,6 +157,7 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(baseShaOrRoot string cmdObj.AddEnvVars( daemon.DaemonKindEnvKey+"="+string(daemon.InteractiveRebase), daemon.RebaseTODOEnvKey+"="+todo, + daemon.PrependLinesEnvKey+"="+prependLines, "DEBUG="+debug, "LANG=en_US.UTF-8", // Force using EN as language "LC_ALL=en_US.UTF-8", // Force using EN as language @@ -273,12 +278,12 @@ func (self *RebaseCommands) BeginInteractiveRebaseForCommit(commits []*models.Co return err } - return self.PrepareInteractiveRebaseCommand(sha, todo, true).Run() + return self.PrepareInteractiveRebaseCommand(sha, todo, true, false).Run() } // RebaseBranch interactive rebases onto a branch func (self *RebaseCommands) RebaseBranch(branchName string) error { - return self.PrepareInteractiveRebaseCommand(branchName, nil, false).Run() + return self.PrepareInteractiveRebaseCommand(branchName, nil, false, false).Run() } func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj { @@ -363,7 +368,7 @@ func (self *RebaseCommands) DiscardOldFileChanges(commits []*models.Commit, comm func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error { todoLines := self.BuildTodoLinesSingleAction(commits, "pick") - return self.PrepareInteractiveRebaseCommand("HEAD", todoLines, false).Run() + return self.PrepareInteractiveRebaseCommand("HEAD", todoLines, false, false).Run() } func (self *RebaseCommands) buildTodo(todoLines []TodoLine) string { From a3fdf91714c460f0496627e48a86a697107f40fb Mon Sep 17 00:00:00 2001 From: AzraelSec Date: Wed, 29 Mar 2023 00:53:14 +0200 Subject: [PATCH 2/9] feat: allow to perform a rebase with breaking before the first commit --- pkg/commands/git_commands/rebase.go | 5 +++ .../helpers/merge_and_rebase_helper.go | 39 +++++++++++++------ pkg/i18n/chinese.go | 2 - pkg/i18n/dutch.go | 2 - pkg/i18n/english.go | 10 +++-- pkg/i18n/korean.go | 2 - pkg/i18n/polish.go | 2 - 7 files changed, 40 insertions(+), 22 deletions(-) diff --git a/pkg/commands/git_commands/rebase.go b/pkg/commands/git_commands/rebase.go index 4e4892d8d..18072779e 100644 --- a/pkg/commands/git_commands/rebase.go +++ b/pkg/commands/git_commands/rebase.go @@ -126,6 +126,11 @@ func (self *RebaseCommands) InteractiveRebaseBreakAfter(commits []*models.Commit return self.PrepareInteractiveRebaseCommand(sha, todo, true, false).Run() } +func (self *RebaseCommands) EditRebase(branchRef string) error { + commands := []TodoLine{{Action: "break"}} + return self.PrepareInteractiveRebaseCommand(branchRef, commands, false, true).Run() +} + // 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 diff --git a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go index b70f7b2ed..9cb8f4a15 100644 --- a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go +++ b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go @@ -201,22 +201,39 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error { if ref == checkedOutBranch { return self.c.ErrorMsg(self.c.Tr.CantRebaseOntoSelf) } - prompt := utils.ResolvePlaceholderString( - self.c.Tr.ConfirmRebase, + menuItems := []*types.MenuItem{ + { + Label: self.c.Tr.SimpleRebase, + Key: 's', + OnPress: func() error { + self.c.LogAction(self.c.Tr.Actions.RebaseBranch) + err := self.git.Rebase.RebaseBranch(ref) + return self.CheckMergeOrRebase(err) + }, + }, + { + Label: self.c.Tr.InteractiveRebase, + Key: 'i', + Tooltip: self.c.Tr.InteractiveRebaseTooltip, + OnPress: func() error { + self.c.LogAction(self.c.Tr.Actions.RebaseBranch) + err := self.git.Rebase.EditRebase(ref) + return self.CheckMergeOrRebase(err) + }, + }, + } + + title := utils.ResolvePlaceholderString( + self.c.Tr.RebasingTitle, map[string]string{ "checkedOutBranch": checkedOutBranch, - "selectedBranch": ref, + "ref": ref, }, ) - return self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.RebasingTitle, - Prompt: prompt, - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.RebaseBranch) - err := self.git.Rebase.RebaseBranch(ref) - return self.CheckMergeOrRebase(err) - }, + return self.c.Menu(types.CreateMenuOptions{ + Title: title, + Items: menuItems, }) } diff --git a/pkg/i18n/chinese.go b/pkg/i18n/chinese.go index dbc8122e9..2d300caf7 100644 --- a/pkg/i18n/chinese.go +++ b/pkg/i18n/chinese.go @@ -201,8 +201,6 @@ func chineseTranslationSet() TranslationSet { ReflogCommitsTitle: "Reflog 页面", GlobalTitle: "全局键绑定", ConflictsResolved: "已解决所有冲突。是否继续?", - RebasingTitle: "变基", - ConfirmRebase: "您确定要将分支 {{.checkedOutBranch}} 变基到 {{.selectedBranch}} 吗?", ConfirmMerge: "您确定要将分支 {{.selectedBranch}} 合并到 {{.checkedOutBranch}} 吗?", FwdNoUpstream: "此分支没有上游,无法快进", FwdNoLocalUpstream: "此分支的远程未在本地注册,无法快进", diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index d8fcb5338..f52908cea 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -166,9 +166,7 @@ func dutchTranslationSet() TranslationSet { ReflogCommitsTitle: "Reflog", GlobalTitle: "Globale Sneltoetsen", ConflictsResolved: "alle merge conflicten zijn opgelost. Wilt je verder gaan?", - RebasingTitle: "Rebasen", MergingTitle: "Mergen", - ConfirmRebase: "Weet je zeker dat je '{{.checkedOutBranch}}' op '{{.selectedBranch}}' wil rebasen?", ConfirmMerge: "Weet je zeker dat je '{{.selectedBranch}}' in '{{.checkedOutBranch}}' wil mergen?", FwdNoUpstream: "Kan niet de branch vooruitspoelen zonder upstream", FwdCommitsToPush: "Je kan niet vooruitspoelen als de branch geen nieuwe commits heeft", diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 80179f365..b8cbc46b1 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -209,7 +209,9 @@ type TranslationSet struct { ReflogCommitsTitle string ConflictsResolved string RebasingTitle string - ConfirmRebase string + SimpleRebase string + InteractiveRebase string + InteractiveRebaseTooltip string ConfirmMerge string FwdNoUpstream string FwdNoLocalUpstream string @@ -877,8 +879,10 @@ func EnglishTranslationSet() TranslationSet { ReflogCommitsTitle: "Reflog", GlobalTitle: "Global Keybindings", ConflictsResolved: "all merge conflicts resolved. Continue?", - RebasingTitle: "Rebasing", - ConfirmRebase: "Are you sure you want to rebase '{{.checkedOutBranch}}' on top of '{{.selectedBranch}}'?", + RebasingTitle: "Rebase '{{.checkedOutBranch}}' onto '{{.ref}}'", + SimpleRebase: "Simple rebase", + InteractiveRebase: "Interactive rebase", + InteractiveRebaseTooltip: "Begin an interactive rebase with a break at the start, so you can update the TODO commits before continuing", ConfirmMerge: "Are you sure you want to merge '{{.selectedBranch}}' into '{{.checkedOutBranch}}'?", FwdNoUpstream: "Cannot fast-forward a branch with no upstream", FwdNoLocalUpstream: "Cannot fast-forward a branch whose remote is not registered locally", diff --git a/pkg/i18n/korean.go b/pkg/i18n/korean.go index fa649bcc0..88e7db8f6 100644 --- a/pkg/i18n/korean.go +++ b/pkg/i18n/korean.go @@ -202,8 +202,6 @@ func koreanTranslationSet() TranslationSet { ReflogCommitsTitle: "Reflog", GlobalTitle: "글로벌 키 바인딩", ConflictsResolved: "모든 병합 충돌이 해결되었습니다. 계속 할까요?", - RebasingTitle: "리베이스 중", - ConfirmRebase: "정말로 '{{.checkedOutBranch}}' 을(를) '{{.selectedBranch}}'에 리베이스 하시겠습니까?", ConfirmMerge: "정말로 '{{.selectedBranch}}' 을(를) '{{.checkedOutBranch}}'에 병합하시겠습니까?", FwdNoUpstream: "Cannot fast-forward a branch with no upstream", FwdNoLocalUpstream: "Cannot fast-forward a branch whose remote is not registered locally", diff --git a/pkg/i18n/polish.go b/pkg/i18n/polish.go index 3db26a22c..00ed9f38f 100644 --- a/pkg/i18n/polish.go +++ b/pkg/i18n/polish.go @@ -112,9 +112,7 @@ func polishTranslationSet() TranslationSet { FileStagingRequirements: "Można tylko zatwierdzić pojedyncze linie dla śledzonych plików z niezatwierdzonymi zmianami", StagingTitle: "Poczekalnia", ReturnToFilesPanel: "wróć do panelu plików", - RebasingTitle: "Zmiana bazy", MergingTitle: "Scalanie", - ConfirmRebase: "Czy napewno chcesz zmienić bazę '{{.checkedOutBranch}}' na '{{.selectedBranch}}'?", ConfirmMerge: "Czy na pewno chcesz scalić '{{.selectedBranch}}' do '{{.checkedOutBranch}}'?", FwdNoUpstream: "Nie można przewinąć gałęzi bez gałęzi nadrzędnej", FwdCommitsToPush: "Nie można przewinąć gałęzi z commitami do wysłania", From 3422b1e21875461ecf76309df66b0f5b2cd929ba Mon Sep 17 00:00:00 2001 From: AzraelSec Date: Wed, 29 Mar 2023 15:45:06 +0200 Subject: [PATCH 3/9] test: update the UI to follow the new rebase type selection instead of confirm the previous popup --- pkg/integration/tests/branch/rebase.go | 6 +++--- pkg/integration/tests/branch/rebase_and_drop.go | 6 +++--- pkg/integration/tests/branch/rebase_does_not_autosquash.go | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/integration/tests/branch/rebase.go b/pkg/integration/tests/branch/rebase.go index 07b9037a3..f2c0e3287 100644 --- a/pkg/integration/tests/branch/rebase.go +++ b/pkg/integration/tests/branch/rebase.go @@ -30,9 +30,9 @@ var Rebase = NewIntegrationTest(NewIntegrationTestArgs{ SelectNextItem(). Press(keys.Branches.RebaseBranch) - t.ExpectPopup().Confirmation(). - Title(Equals("Rebasing")). - Content(Contains("Are you sure you want to rebase 'first-change-branch' on top of 'second-change-branch'?")). + t.ExpectPopup().Menu(). + Title(Equals("Rebase 'first-change-branch' onto 'second-change-branch'")). + Select(Contains("Simple rebase")). Confirm() t.Common().AcknowledgeConflicts() diff --git a/pkg/integration/tests/branch/rebase_and_drop.go b/pkg/integration/tests/branch/rebase_and_drop.go index 298636c59..c1fc6ab2f 100644 --- a/pkg/integration/tests/branch/rebase_and_drop.go +++ b/pkg/integration/tests/branch/rebase_and_drop.go @@ -36,9 +36,9 @@ var RebaseAndDrop = NewIntegrationTest(NewIntegrationTestArgs{ SelectNextItem(). Press(keys.Branches.RebaseBranch) - t.ExpectPopup().Confirmation(). - Title(Equals("Rebasing")). - Content(Contains("Are you sure you want to rebase 'first-change-branch' on top of 'second-change-branch'?")). + t.ExpectPopup().Menu(). + Title(Equals("Rebase 'first-change-branch' onto 'second-change-branch'")). + Select(Contains("Simple rebase")). Confirm() t.Views().Information().Content(Contains("rebasing")) diff --git a/pkg/integration/tests/branch/rebase_does_not_autosquash.go b/pkg/integration/tests/branch/rebase_does_not_autosquash.go index 78c957a13..4465bc93b 100644 --- a/pkg/integration/tests/branch/rebase_does_not_autosquash.go +++ b/pkg/integration/tests/branch/rebase_does_not_autosquash.go @@ -39,9 +39,9 @@ var RebaseDoesNotAutosquash = NewIntegrationTest(NewIntegrationTestArgs{ SelectNextItem(). Press(keys.Branches.RebaseBranch) - t.ExpectPopup().Confirmation(). - Title(Equals("Rebasing")). - Content(Contains("Are you sure you want to rebase 'my-branch' on top of 'master'?")). + t.ExpectPopup().Menu(). + Title(Equals("Rebase 'my-branch' onto 'master'")). + Select(Contains("Simple rebase")). Confirm() t.Views().Commits().Lines( From 711be788113bf42a033bcbfe6e3efb77bcfaefe9 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 2 Apr 2023 11:37:02 +1000 Subject: [PATCH 4/9] extract out function --- pkg/app/daemon/daemon.go | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/pkg/app/daemon/daemon.go b/pkg/app/daemon/daemon.go index 5490a25e5..e040e79c2 100644 --- a/pkg/app/daemon/daemon.go +++ b/pkg/app/daemon/daemon.go @@ -79,25 +79,11 @@ type rebaseDaemon struct { func (self *rebaseDaemon) Run() error { self.c.Log.Info("Lazygit invoked as interactive rebase demon") self.c.Log.Info("args: ", os.Args) - filePath := os.Args[1] + path := os.Args[1] - if strings.HasSuffix(filePath, "git-rebase-todo") { - todoEnvKey := os.Getenv(RebaseTODOEnvKey) - - var todoContent []byte - if v := os.Getenv(PrependLinesEnvKey); v != "" { - existingContent, err := os.ReadFile(filePath) - if err != nil { - return err - } - - todoContent = append([]byte(todoEnvKey), existingContent...) - } else { - todoContent = []byte(todoEnvKey) - } - - return os.WriteFile(filePath, todoContent, 0o644) - } else if strings.HasSuffix(filePath, filepath.Join(gitDir(), "COMMIT_EDITMSG")) { // TODO: test + if strings.HasSuffix(path, "git-rebase-todo") { + return self.writeTodoFile(path) + } else if strings.HasSuffix(path, filepath.Join(gitDir(), "COMMIT_EDITMSG")) { // TODO: test // if we are rebasing and squashing, we'll see a COMMIT_EDITMSG // but in this case we don't need to edit it, so we'll just return } else { @@ -107,6 +93,22 @@ func (self *rebaseDaemon) Run() error { return nil } +func (self *rebaseDaemon) writeTodoFile(path string) error { + todoContent := []byte(os.Getenv(RebaseTODOEnvKey)) + + prependLines := os.Getenv(PrependLinesEnvKey) != "" + if prependLines { + existingContent, err := os.ReadFile(path) + if err != nil { + return err + } + + todoContent = append(todoContent, existingContent...) + } + + return os.WriteFile(path, todoContent, 0o644) +} + func gitDir() string { dir := env.GetGitDirEnv() if dir == "" { From ddcd6be2458763c252c10e3c45342b274a6bf668 Mon Sep 17 00:00:00 2001 From: AzraelSec Date: Sun, 2 Apr 2023 11:07:40 +0200 Subject: [PATCH 5/9] refactor: introduce a struct to pack the `PrepareInteractiveRebaseCommand` function --- pkg/commands/git_commands/patch.go | 6 ++- pkg/commands/git_commands/rebase.go | 59 ++++++++++++++++++++++------- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/pkg/commands/git_commands/patch.go b/pkg/commands/git_commands/patch.go index b5d7bf313..8c956529c 100644 --- a/pkg/commands/git_commands/patch.go +++ b/pkg/commands/git_commands/patch.go @@ -111,7 +111,11 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s } }) - err := self.rebase.PrepareInteractiveRebaseCommand(commits[baseIndex].Sha, todoLines, true, false).Run() + err := self.rebase.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ + baseShaOrRoot: commits[baseIndex].Sha, + todoLines: todoLines, + overrideEditor: true, + }).Run() if err != nil { return err } diff --git a/pkg/commands/git_commands/rebase.go b/pkg/commands/git_commands/rebase.go index 18072779e..cffc5d68a 100644 --- a/pkg/commands/git_commands/rebase.go +++ b/pkg/commands/git_commands/rebase.go @@ -60,7 +60,10 @@ func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index return nil, err } - return self.PrepareInteractiveRebaseCommand(sha, todo, false, false), nil + return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ + baseShaOrRoot: sha, + todoLines: todo, + }), nil } func (self *RebaseCommands) ResetCommitAuthor(commits []*models.Commit, index int) error { @@ -104,7 +107,11 @@ func (self *RebaseCommands) MoveCommitDown(commits []*models.Commit, index int) baseShaOrRoot := getBaseShaOrRoot(commits, index+2) - return self.PrepareInteractiveRebaseCommand(baseShaOrRoot, todoLines, true, false).Run() + return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ + baseShaOrRoot: baseShaOrRoot, + todoLines: todoLines, + overrideEditor: true, + }).Run() } func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index int, action string) error { @@ -113,7 +120,11 @@ func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index in return err } - return self.PrepareInteractiveRebaseCommand(sha, todo, true, false).Run() + return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ + baseShaOrRoot: sha, + todoLines: todo, + overrideEditor: true, + }).Run() } func (self *RebaseCommands) InteractiveRebaseBreakAfter(commits []*models.Commit, index int) error { @@ -123,22 +134,37 @@ func (self *RebaseCommands) InteractiveRebaseBreakAfter(commits []*models.Commit } todo = append(todo, TodoLine{Action: "break", Commit: nil}) - return self.PrepareInteractiveRebaseCommand(sha, todo, true, false).Run() + return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ + baseShaOrRoot: sha, + todoLines: todo, + overrideEditor: true, + }).Run() } func (self *RebaseCommands) EditRebase(branchRef string) error { commands := []TodoLine{{Action: "break"}} - return self.PrepareInteractiveRebaseCommand(branchRef, commands, false, true).Run() + return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ + baseShaOrRoot: branchRef, + todoLines: commands, + prepend: true, + }).Run() +} + +type PrepareInteractiveRebaseCommandOpts struct { + baseShaOrRoot string + todoLines []TodoLine + overrideEditor bool + prepend bool } // 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(baseShaOrRoot string, todoLines []TodoLine, overrideEditor bool, prepend bool) oscommands.ICmdObj { - todo := self.buildTodo(todoLines) +func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteractiveRebaseCommandOpts) oscommands.ICmdObj { + todo := self.buildTodo(opts.todoLines) ex := oscommands.GetLazygitPath() prependLines := "" - if prepend { + if opts.prepend { prependLines = "TRUE" } @@ -147,7 +173,7 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(baseShaOrRoot string debug = "TRUE" } - cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash %s", baseShaOrRoot) + cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash %s", opts.baseShaOrRoot) self.Log.WithField("command", cmdStr).Debug("RunCommand") cmdObj := self.cmd.New(cmdStr) @@ -169,7 +195,7 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(baseShaOrRoot string "GIT_SEQUENCE_EDITOR="+gitSequenceEditor, ) - if overrideEditor { + if opts.overrideEditor { cmdObj.AddEnvVars("GIT_EDITOR=" + ex) } @@ -283,12 +309,16 @@ func (self *RebaseCommands) BeginInteractiveRebaseForCommit(commits []*models.Co return err } - return self.PrepareInteractiveRebaseCommand(sha, todo, true, false).Run() + return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ + baseShaOrRoot: sha, + todoLines: todo, + overrideEditor: true, + }).Run() } // RebaseBranch interactive rebases onto a branch func (self *RebaseCommands) RebaseBranch(branchName string) error { - return self.PrepareInteractiveRebaseCommand(branchName, nil, false, false).Run() + return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{baseShaOrRoot: branchName}).Run() } func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj { @@ -373,7 +403,10 @@ func (self *RebaseCommands) DiscardOldFileChanges(commits []*models.Commit, comm func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error { todoLines := self.BuildTodoLinesSingleAction(commits, "pick") - return self.PrepareInteractiveRebaseCommand("HEAD", todoLines, false, false).Run() + return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ + baseShaOrRoot: "HEAD", + todoLines: todoLines, + }).Run() } func (self *RebaseCommands) buildTodo(todoLines []TodoLine) string { From b82b6a2992b56f8cacc9e9ec9a886e6af51ce99e Mon Sep 17 00:00:00 2001 From: AzraelSec Date: Sun, 2 Apr 2023 20:45:21 +0200 Subject: [PATCH 6/9] test: add integration test to verify the interactive rebase correctly work --- .../advanced_interactive_rebase.go | 66 +++++++++++++++++++ pkg/integration/tests/test_list.go | 1 + 2 files changed, 67 insertions(+) create mode 100644 pkg/integration/tests/interactive_rebase/advanced_interactive_rebase.go diff --git a/pkg/integration/tests/interactive_rebase/advanced_interactive_rebase.go b/pkg/integration/tests/interactive_rebase/advanced_interactive_rebase.go new file mode 100644 index 000000000..e4788973c --- /dev/null +++ b/pkg/integration/tests/interactive_rebase/advanced_interactive_rebase.go @@ -0,0 +1,66 @@ +package interactive_rebase + +import ( + "fmt" + + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +const ( + BASE_BRANCH = "base-branch" + TOP_BRANCH = "top-branch" + BASE_COMMIT = "base-commit" + TOP_COMMIT = "top-commit" +) + +var AdvancedInteractiveRebase = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "It begins an interactive rebase and verifies to have the possibility of editing the commits of the branch before proceeding with the actual rebase", + ExtraCmdArgs: "", + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell. + NewBranch(BASE_BRANCH). + EmptyCommit(BASE_COMMIT). + NewBranch(TOP_BRANCH). + EmptyCommit(TOP_COMMIT) + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains(TOP_COMMIT), + Contains(BASE_COMMIT), + ) + + t.Views().Branches(). + Focus(). + NavigateToLine(Contains(BASE_BRANCH)). + Press(keys.Branches.RebaseBranch) + + t.ExpectPopup().Menu(). + Title(Equals(fmt.Sprintf("Rebase '%s' onto '%s'", TOP_BRANCH, BASE_BRANCH))). + Select(Contains("Interactive rebase")). + Confirm() + + t.Views().Commits(). + Focus(). + Lines( + Contains(TOP_COMMIT), + Contains(BASE_COMMIT).Contains("YOU ARE HERE"), + ). + NavigateToLine(Contains(TOP_COMMIT)). + Press(keys.Universal.Edit). + Lines( + Contains(TOP_COMMIT).Contains("edit"), + Contains(BASE_COMMIT).Contains("YOU ARE HERE"), + ). + Tap(func() { + t.Common().ContinueRebase() + }). + Lines( + Contains(TOP_COMMIT).Contains("YOU ARE HERE"), + Contains(BASE_COMMIT), + ) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index a8959ee24..d4da79732 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -83,6 +83,7 @@ var tests = []*components.IntegrationTest{ filter_by_path.CliArg, filter_by_path.SelectFile, filter_by_path.TypeFile, + interactive_rebase.AdvancedInteractiveRebase, interactive_rebase.AmendFirstCommit, interactive_rebase.AmendHeadCommitDuringRebase, interactive_rebase.AmendMerge, From 8f1f7128417169eb2f3692a49a06996980130c97 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 15 Apr 2023 17:25:58 +1000 Subject: [PATCH 7/9] use lowercase text for menu items (as we're still yet to standardise on 'Sentence case') --- pkg/i18n/english.go | 4 ++-- pkg/integration/tests/branch/rebase.go | 2 +- pkg/integration/tests/branch/rebase_and_drop.go | 2 +- pkg/integration/tests/branch/rebase_does_not_autosquash.go | 2 +- .../tests/interactive_rebase/advanced_interactive_rebase.go | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index b8cbc46b1..4d384da71 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -880,8 +880,8 @@ func EnglishTranslationSet() TranslationSet { GlobalTitle: "Global Keybindings", ConflictsResolved: "all merge conflicts resolved. Continue?", RebasingTitle: "Rebase '{{.checkedOutBranch}}' onto '{{.ref}}'", - SimpleRebase: "Simple rebase", - InteractiveRebase: "Interactive rebase", + SimpleRebase: "simple rebase", + InteractiveRebase: "interactive rebase", InteractiveRebaseTooltip: "Begin an interactive rebase with a break at the start, so you can update the TODO commits before continuing", ConfirmMerge: "Are you sure you want to merge '{{.selectedBranch}}' into '{{.checkedOutBranch}}'?", FwdNoUpstream: "Cannot fast-forward a branch with no upstream", diff --git a/pkg/integration/tests/branch/rebase.go b/pkg/integration/tests/branch/rebase.go index f2c0e3287..c34e73c2d 100644 --- a/pkg/integration/tests/branch/rebase.go +++ b/pkg/integration/tests/branch/rebase.go @@ -32,7 +32,7 @@ var Rebase = NewIntegrationTest(NewIntegrationTestArgs{ t.ExpectPopup().Menu(). Title(Equals("Rebase 'first-change-branch' onto 'second-change-branch'")). - Select(Contains("Simple rebase")). + Select(Contains("simple rebase")). Confirm() t.Common().AcknowledgeConflicts() diff --git a/pkg/integration/tests/branch/rebase_and_drop.go b/pkg/integration/tests/branch/rebase_and_drop.go index c1fc6ab2f..8a1ade2fe 100644 --- a/pkg/integration/tests/branch/rebase_and_drop.go +++ b/pkg/integration/tests/branch/rebase_and_drop.go @@ -38,7 +38,7 @@ var RebaseAndDrop = NewIntegrationTest(NewIntegrationTestArgs{ t.ExpectPopup().Menu(). Title(Equals("Rebase 'first-change-branch' onto 'second-change-branch'")). - Select(Contains("Simple rebase")). + Select(Contains("simple rebase")). Confirm() t.Views().Information().Content(Contains("rebasing")) diff --git a/pkg/integration/tests/branch/rebase_does_not_autosquash.go b/pkg/integration/tests/branch/rebase_does_not_autosquash.go index 4465bc93b..2638bec7b 100644 --- a/pkg/integration/tests/branch/rebase_does_not_autosquash.go +++ b/pkg/integration/tests/branch/rebase_does_not_autosquash.go @@ -41,7 +41,7 @@ var RebaseDoesNotAutosquash = NewIntegrationTest(NewIntegrationTestArgs{ t.ExpectPopup().Menu(). Title(Equals("Rebase 'my-branch' onto 'master'")). - Select(Contains("Simple rebase")). + Select(Contains("simple rebase")). Confirm() t.Views().Commits().Lines( diff --git a/pkg/integration/tests/interactive_rebase/advanced_interactive_rebase.go b/pkg/integration/tests/interactive_rebase/advanced_interactive_rebase.go index e4788973c..ba0055276 100644 --- a/pkg/integration/tests/interactive_rebase/advanced_interactive_rebase.go +++ b/pkg/integration/tests/interactive_rebase/advanced_interactive_rebase.go @@ -40,7 +40,7 @@ var AdvancedInteractiveRebase = NewIntegrationTest(NewIntegrationTestArgs{ t.ExpectPopup().Menu(). Title(Equals(fmt.Sprintf("Rebase '%s' onto '%s'", TOP_BRANCH, BASE_BRANCH))). - Select(Contains("Interactive rebase")). + Select(Contains("interactive rebase")). Confirm() t.Views().Commits(). From 28501fbf77c18189bb1016da2ff17d15a32bc1bd Mon Sep 17 00:00:00 2001 From: AzraelSec Date: Sat, 15 Apr 2023 10:42:36 +0200 Subject: [PATCH 8/9] chore: add focus on local commits after interactively rebase --- pkg/gui/controllers/helpers/merge_and_rebase_helper.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go index 9cb8f4a15..981b85a0d 100644 --- a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go +++ b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go @@ -218,7 +218,10 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error { OnPress: func() error { self.c.LogAction(self.c.Tr.Actions.RebaseBranch) err := self.git.Rebase.EditRebase(ref) - return self.CheckMergeOrRebase(err) + if err = self.CheckMergeOrRebase(err); err != nil { + return err + } + return self.c.PushContext(self.contexts.LocalCommits) }, }, } From 08a445eb9dbf447de4121220712279458e9020a5 Mon Sep 17 00:00:00 2001 From: AzraelSec Date: Sat, 15 Apr 2023 11:01:55 +0200 Subject: [PATCH 9/9] test: check focus on commits after performing an advanced rebase --- .../tests/interactive_rebase/advanced_interactive_rebase.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/integration/tests/interactive_rebase/advanced_interactive_rebase.go b/pkg/integration/tests/interactive_rebase/advanced_interactive_rebase.go index ba0055276..5ebc86d52 100644 --- a/pkg/integration/tests/interactive_rebase/advanced_interactive_rebase.go +++ b/pkg/integration/tests/interactive_rebase/advanced_interactive_rebase.go @@ -42,9 +42,8 @@ var AdvancedInteractiveRebase = NewIntegrationTest(NewIntegrationTestArgs{ Title(Equals(fmt.Sprintf("Rebase '%s' onto '%s'", TOP_BRANCH, BASE_BRANCH))). Select(Contains("interactive rebase")). Confirm() - t.Views().Commits(). - Focus(). + IsFocused(). Lines( Contains(TOP_COMMIT), Contains(BASE_COMMIT).Contains("YOU ARE HERE"),