Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions pkg/gui/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand All @@ -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(),
},
Expand Down Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions pkg/gui/types/modes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
1 change: 1 addition & 0 deletions pkg/integration/tests/test_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ var tests = []*components.IntegrationTest{
worktree.AssociateBranchRebase,
worktree.BareRepo,
worktree.BareRepoWorktreeConfig,
worktree.CherryPickAcrossWorktrees,
worktree.Crud,
worktree.CustomCommand,
worktree.DetachWorktreeFromBranch,
Expand Down
60 changes: 60 additions & 0 deletions pkg/integration/tests/worktree/cherry_pick_across_worktrees.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package worktree

import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)

var CherryPickAcrossWorktrees = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Copy a commit in one worktree and paste it in another worktree of the same repo",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.NewBranch("mybranch")
shell.EmptyCommit("base")
// the linked worktree's branch stays at "base"
shell.AddWorktree("mybranch", "../linked-worktree", "newbranch")
shell.EmptyCommit("one")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Lines(
Contains("one").IsSelected(),
Contains("base"),
).
Press(keys.Commits.CherryPickCopy)

t.Views().Information().Content(Contains("1 commit copied"))

t.Views().Worktrees().
Focus().
Lines(
Contains("(main worktree)").IsSelected(),
Contains("linked-worktree"),
).
NavigateToLine(Contains("linked-worktree")).
Press(keys.Universal.Select)

t.Views().Commits().
Focus().
Lines(
Contains("base"),
).
Press(keys.Commits.PasteCommits)

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?")).
Confirm()

t.Views().Information().Content(DoesNotContain("commit copied"))

t.Views().Commits().
Lines(
Contains("one"),
Contains("base").IsSelected(),
)
},
})