From a642395e9f7794e77eac350b063cb975a49ba6f4 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Thu, 6 Apr 2023 15:33:00 +0200 Subject: [PATCH] Allow cherry-picking commits during a rebase This can be useful when you know that a cherry-picked commit would conflict at the tip of your branch, but doesn't at the beginning of the branch (or somewhere in the middle). In that case you want to be able to edit the commit before where you want to insert the cherry-picked commits, and then paste to insert them into the todo list at that point. --- pkg/commands/git_commands/rebase.go | 14 +++ .../controllers/helpers/cherry_pick_helper.go | 13 +++ .../cherry_pick/cherry_pick_during_rebase.go | 87 +++++++++++++++++++ pkg/integration/tests/test_list.go | 1 + 4 files changed, 115 insertions(+) create mode 100644 pkg/integration/tests/cherry_pick/cherry_pick_during_rebase.go diff --git a/pkg/commands/git_commands/rebase.go b/pkg/commands/git_commands/rebase.go index 66adee2af..2dd1ee886 100644 --- a/pkg/commands/git_commands/rebase.go +++ b/pkg/commands/git_commands/rebase.go @@ -457,6 +457,20 @@ func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error { }).Run() } +// CherryPickCommitsDuringRebase simply prepends the given commits to the existing git-rebase-todo file +func (self *RebaseCommands) CherryPickCommitsDuringRebase(commits []*models.Commit) error { + todoLines := lo.Map(commits, func(commit *models.Commit, _ int) daemon.TodoLine { + return daemon.TodoLine{ + Action: "pick", + Commit: commit, + } + }) + + todo := daemon.TodoLinesToString(todoLines) + filePath := filepath.Join(self.repoPaths.worktreeGitDirPath, "rebase-merge/git-rebase-todo") + return utils.PrependStrToTodoFile(filePath, []byte(todo)) +} + // we can't start an interactive rebase from the first commit without passing the // '--root' arg func getBaseShaOrRoot(commits []*models.Commit, index int) string { diff --git a/pkg/gui/controllers/helpers/cherry_pick_helper.go b/pkg/gui/controllers/helpers/cherry_pick_helper.go index e27e469b6..4f455ca30 100644 --- a/pkg/gui/controllers/helpers/cherry_pick_helper.go +++ b/pkg/gui/controllers/helpers/cherry_pick_helper.go @@ -76,6 +76,19 @@ func (self *CherryPickHelper) Paste() error { Title: self.c.Tr.CherryPick, Prompt: self.c.Tr.SureCherryPick, HandleConfirm: func() error { + isInRebase, err := self.c.Git().Status.IsInInteractiveRebase() + if err != nil { + return err + } + if isInRebase { + if err := self.c.Git().Rebase.CherryPickCommitsDuringRebase(self.getData().CherryPickedCommits); err != nil { + return err + } + return self.c.Refresh(types.RefreshOptions{ + Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS}, + }) + } + return self.c.WithWaitingStatus(self.c.Tr.CherryPickingStatus, func(gocui.Task) error { self.c.LogAction(self.c.Tr.Actions.CherryPick) err := self.c.Git().Rebase.CherryPickCommits(self.getData().CherryPickedCommits) diff --git a/pkg/integration/tests/cherry_pick/cherry_pick_during_rebase.go b/pkg/integration/tests/cherry_pick/cherry_pick_during_rebase.go new file mode 100644 index 000000000..4cf32bad3 --- /dev/null +++ b/pkg/integration/tests/cherry_pick/cherry_pick_during_rebase.go @@ -0,0 +1,87 @@ +package cherry_pick + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var CherryPickDuringRebase = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Cherry pick commits from the subcommits view during a rebase", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell. + EmptyCommit("base"). + NewBranch("first-branch"). + NewBranch("second-branch"). + Checkout("first-branch"). + EmptyCommit("one"). + EmptyCommit("two"). + Checkout("second-branch"). + EmptyCommit("three"). + EmptyCommit("four"). + Checkout("first-branch") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Branches(). + Focus(). + Lines( + Contains("first-branch"), + Contains("second-branch"), + Contains("master"), + ). + SelectNextItem(). + PressEnter() + + t.Views().SubCommits(). + IsFocused(). + Lines( + Contains("four").IsSelected(), + Contains("three"), + Contains("base"), + ). + // copy commit 'three' + SelectNextItem(). + Press(keys.Commits.CherryPickCopy) + + t.Views().Information().Content(Contains("1 commit copied")) + + t.Views().Commits(). + Focus(). + Lines( + Contains("CI two").IsSelected(), + Contains("CI one"), + Contains("CI base"), + ). + SelectNextItem(). + Press(keys.Universal.Edit). + Lines( + Contains("pick CI two"), + Contains(" CI <-- YOU ARE HERE --- one").IsSelected(), + Contains(" CI base"), + ). + Press(keys.Commits.PasteCommits). + Tap(func() { + t.ExpectPopup().Alert(). + Title(Equals("Cherry-pick")). + Content(Contains("Are you sure you want to cherry-pick the copied commits onto this branch?")). + Confirm() + }). + Lines( + Contains("pick CI two"), + Contains("pick CI three"), + Contains(" CI <-- YOU ARE HERE --- one"), + Contains(" CI base"), + ). + Tap(func() { + t.Common().ContinueRebase() + }). + Lines( + Contains("CI two"), + Contains("CI three"), + Contains("CI one"), + Contains("CI base"), + ) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index b820555f2..d16da747d 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -55,6 +55,7 @@ var tests = []*components.IntegrationTest{ branch.Suggestions, cherry_pick.CherryPick, cherry_pick.CherryPickConflicts, + cherry_pick.CherryPickDuringRebase, commit.AddCoAuthor, commit.Amend, commit.Commit,