From a869854c63027a767ae5b64f7a572a0598530f41 Mon Sep 17 00:00:00 2001 From: Paul Nodet <5941125+pnodet@users.noreply.github.com> Date: Thu, 11 Jun 2026 03:45:36 +0200 Subject: [PATCH] Share cherry-pick state across worktrees of the same repo Copied commits are referenced by hash, and hashes resolve identically in every worktree of a repo (shared object database), so there's no reason pasting shouldn't work after switching worktrees. Move the clipboard out of the per-worktree GuiRepoState into a per-repo SharedRepoState, keyed by the repo's common git dir; every worktree's Modes.CherryPicking now points at the same instance. Submodules and unrelated repos have different git dirs, so they keep isolated clipboards. As a side effect, cancelling a cherry-pick in one worktree now correctly clears it in all of them. --- pkg/gui/gui.go | 26 +++++++++++++++++-- pkg/gui/types/modes.go | 9 +++++-- .../worktree/cherry_pick_across_worktrees.go | 3 --- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index e2881cca1..3ae4da7d5 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -76,7 +76,11 @@ type Gui struct { // 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. - RepoStateMap map[Repo]*GuiRepoState + RepoStateMap map[Repo]*GuiRepoState + // Holds state shared between all worktrees of the same repo, keyed by the + // repo's common git dir (one entry per repo, where RepoStateMap has one + // entry per worktree). + sharedRepoStateMap map[Repo]*SharedRepoState Config config.AppConfigurer Updater *updates.Updater statusManager *status.StatusManager @@ -259,6 +263,14 @@ type GuiRepoState struct { var _ types.IRepoStateAccessor = new(GuiRepoState) +// SharedRepoState is state shared between all worktrees of the same repo. +// Unlike GuiRepoState, of which we keep one instance per worktree, there is +// only one instance of this per repo; e.g. commits copied for cherry-picking +// in one worktree can be pasted in another. +type SharedRepoState struct { + CherryPicking *cherrypicking.CherryPicking +} + func (self *GuiRepoState) GetViewsSetup() bool { return self.ViewsSetup } @@ -591,6 +603,15 @@ func (gui *Gui) resetState(startArgs appTypes.StartArgs) types.Context { return gui.c.Context().Current() } + repoGitDirPath := gui.git.RepoPaths.RepoGitDirPath() + sharedState := gui.sharedRepoStateMap[Repo(repoGitDirPath)] + if sharedState == nil { + sharedState = &SharedRepoState{ + CherryPicking: cherrypicking.New(), + } + gui.sharedRepoStateMap[Repo(repoGitDirPath)] = sharedState + } + contextTree := gui.contextTree() initialScreenMode := initialScreenMode(startArgs, gui.Config) @@ -614,7 +635,7 @@ func (gui *Gui) resetState(startArgs appTypes.StartArgs) types.Context { }, Modes: &types.Modes{ Filtering: filtering.New(startArgs.FilterPath, ""), - CherryPicking: cherrypicking.New(), + CherryPicking: sharedState.CherryPicking, Diffing: diffing.New(), MarkedBaseCommit: marked_base_commit.New(), }, @@ -738,6 +759,7 @@ func NewGui( showRecentRepos: showRecentRepos, RepoPathStack: &utils.StringStack{}, RepoStateMap: map[Repo]*GuiRepoState{}, + sharedRepoStateMap: map[Repo]*SharedRepoState{}, GuiLog: []string{}, // initializing this to true for the time being; it will be reset to the diff --git a/pkg/gui/types/modes.go b/pkg/gui/types/modes.go index a11ed0081..c2f897857 100644 --- a/pkg/gui/types/modes.go +++ b/pkg/gui/types/modes.go @@ -8,8 +8,13 @@ import ( ) type Modes struct { - Filtering filtering.Filtering - CherryPicking *cherrypicking.CherryPicking + Filtering filtering.Filtering + + // Shared between all worktrees of the same repo (see gui.SharedRepoState). + // Mutate it through this pointer, but never replace it, otherwise it is no + // longer shared. + CherryPicking *cherrypicking.CherryPicking + Diffing diffing.Diffing MarkedBaseCommit marked_base_commit.MarkedBaseCommit } diff --git a/pkg/integration/tests/worktree/cherry_pick_across_worktrees.go b/pkg/integration/tests/worktree/cherry_pick_across_worktrees.go index ef5bb0d2a..1c907d930 100644 --- a/pkg/integration/tests/worktree/cherry_pick_across_worktrees.go +++ b/pkg/integration/tests/worktree/cherry_pick_across_worktrees.go @@ -44,7 +44,6 @@ var CherryPickAcrossWorktrees = NewIntegrationTest(NewIntegrationTestArgs{ ). Press(keys.Commits.PasteCommits) - /* EXPECTED: t.ExpectPopup().Alert(). Title(Equals("Cherry-pick")). Content(Contains("Are you sure you want to cherry-pick the 1 copied commit(s) onto this branch?")). @@ -57,7 +56,5 @@ var CherryPickAcrossWorktrees = NewIntegrationTest(NewIntegrationTestArgs{ Contains("one"), Contains("base").IsSelected(), ) - ACTUAL: */ - t.ExpectToast(Equals("Disabled: No copied commits")) }, })