Keep copied commits when switching to another worktree or repo (#6090)

If you copy commits with shift-C and then switch to another worktree,
the copied commits are gone, so you can't paste them there with shift-V.
This is a common workflow: copy commits from the branch that is checked
out in one worktree, maybe drop them there right away, then switch to
the worktree of another branch and paste them.

Create the cherry-picking mode once and share it between all repo
states. This also lets you paste in a different clone of the same repo,
as long as that clone has the copied commits (for example because you
fetched them there). If the current repo doesn't have them, git
cherry-pick fails with "bad object", and we show this as an error.
This commit is contained in:
Stefan Haller
2026-10-05 16:26:20 +02:00
committed by GitHub
4 changed files with 88 additions and 2 deletions
+7 -1
View File
@@ -73,6 +73,11 @@ type Gui struct {
CustomCommandsClient *custom_commands.Client
// Unlike the other modes, the cherry-picking mode is shared by all repo
// states, so that you can copy commits in one worktree or repo and paste
// them in another.
cherryPicking *cherrypicking.CherryPicking
// this is a mapping of repos to gui states, so that we can restore the original
// gui state when returning from a subrepo.
// In repos with multiple worktrees, we store a separate repo state per worktree.
@@ -647,7 +652,7 @@ func (gui *Gui) resetState(startArgs appTypes.StartArgs) types.Context {
},
Modes: &types.Modes{
Filtering: filtering.New(startArgs.FilterPath, ""),
CherryPicking: cherrypicking.New(),
CherryPicking: gui.cherryPicking,
Diffing: diffing.New(),
MarkedBaseCommit: marked_base_commit.New(),
},
@@ -800,6 +805,7 @@ func NewGui(
showRecentRepos: showRecentRepos,
RepoPathStack: &utils.Stack[types.RepoLocation]{},
RepoStateMap: map[Repo]*GuiRepoState{},
cherryPicking: cherrypicking.New(),
GuiLog: []string{},
// initializing this to true for the time being; it will be reset to the
+1 -1
View File
@@ -9,7 +9,7 @@ import (
type Modes struct {
Filtering filtering.Filtering
CherryPicking *cherrypicking.CherryPicking
CherryPicking *cherrypicking.CherryPicking // shared by all repo states; see Gui.cherryPicking
Diffing diffing.Diffing
MarkedBaseCommit marked_base_commit.MarkedBaseCommit
}
@@ -0,0 +1,79 @@
package cherry_pick
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var CherryPickAcrossWorktrees = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Copy commits in one worktree, drop them there, and paste them in another worktree",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.
EmptyCommit("base").
AddWorktree("master", "../linked-worktree", "newbranch").
EmptyCommit("one").
EmptyCommit("two")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Lines(
Contains("two").IsSelected(),
Contains("one"),
Contains("base"),
).
Press(keys.Universal.RangeSelectDown).
Press(keys.Commits.CherryPickCopy).
Tap(func() {
t.Views().Information().Content(Contains("2 commits copied"))
}).
Press(keys.Universal.Remove).
Tap(func() {
t.ExpectPopup().Confirmation().
Title(Equals("Drop commit")).
Content(Contains("Are you sure you want to drop the selected commit(s)?")).
Confirm()
}).
Lines(
Contains("base").IsSelected(),
)
t.Views().Branches().
Focus().
NavigateToLine(Contains("newbranch")).
Press(keys.Universal.Select).
Tap(func() {
t.ExpectPopup().Confirmation().
Title(Equals("Switch to worktree")).
Content(Equals("This branch is checked out by worktree linked-worktree. Do you want to switch to that worktree?")).
Confirm()
}).
Lines(
Contains("newbranch").IsSelected(),
Contains("master (worktree repo)"),
)
t.Views().Information().Content(Contains("2 commits copied"))
t.Views().Commits().
Focus().
Lines(
Contains("base").IsSelected(),
).
Press(keys.Commits.PasteCommits).
Tap(func() {
t.ExpectPopup().Alert().
Title(Equals("Cherry-pick")).
Content(Contains("Are you sure you want to cherry-pick the 2 copied commit(s) onto this branch?")).
Confirm()
}).
Lines(
Contains("two"),
Contains("one"),
Contains("base").IsSelected(),
)
},
})
+1
View File
@@ -93,6 +93,7 @@ var tests = []*components.IntegrationTest{
branch.Suggestions,
branch.UnsetUpstream,
cherry_pick.CherryPick,
cherry_pick.CherryPickAcrossWorktrees,
cherry_pick.CherryPickCommitThatBecomesEmpty,
cherry_pick.CherryPickConflicts,
cherry_pick.CherryPickConflictsEmptyCommitAfterResolving,