From 737fb989670cd6d57fc6b9862a0957c9ddbc6afd Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Mon, 29 Jun 2026 17:47:25 +0200 Subject: [PATCH] Move the new-worktree keybinding from worktrees to universal The command was renamed from "View worktree options" to "New worktree", but its keybinding config key was still 'worktrees.viewWorktreeOptions'. That name no longer matches the command, and the 'worktrees' section made little sense: it held a single binding that isn't even used in the worktrees panel (that panel uses universal.new), only in the branches, remotes, tags, commits, and stash panels. Other keybinding sections are named after the panel they're local to; this one wasn't local to any. Move it to universal.newWorktree, which describes the action and drops the spurious section, and migrate existing configs automatically. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs-master/Config.md | 3 +- pkg/config/app_config.go | 23 ++++++- pkg/config/app_config_test.go | 67 +++++++++++++++++++ pkg/config/user_config.go | 10 +-- .../controllers/basic_commits_controller.go | 2 +- pkg/gui/controllers/branches_controller.go | 2 +- .../controllers/remote_branches_controller.go | 2 +- pkg/gui/controllers/stash_controller.go | 2 +- pkg/gui/controllers/tags_controller.go | 2 +- .../demo/worktree_create_from_branches.go | 2 +- .../tests/worktree/add_for_existing_branch.go | 4 +- .../tests/worktree/add_from_branch.go | 2 +- .../worktree/add_from_branch_detached.go | 2 +- .../tests/worktree/add_from_commit.go | 2 +- .../tests/worktree/add_from_remote_branch.go | 2 +- .../tests/worktree/add_from_stash.go | 2 +- .../tests/worktree/add_from_tag.go | 2 +- .../tests/worktree/location_candidates.go | 2 +- schema-master/config.json | 37 ++++------ 19 files changed, 121 insertions(+), 49 deletions(-) diff --git a/docs-master/Config.md b/docs-master/Config.md index 183a0c6b2..bf0f5c2c6 100644 --- a/docs-master/Config.md +++ b/docs-master/Config.md @@ -689,6 +689,7 @@ keybinding: confirmInEditor: [, ] remove: d new: "n" + newWorktree: w edit: e openFile: o scrollUpMain: [, K, ] @@ -769,8 +770,6 @@ keybinding: fetchRemote: f addForkRemote: F sortOrder: s - worktrees: - viewWorktreeOptions: w commits: squashDown: s renameCommit: r diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index e7267158a..313d71dcf 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -287,6 +287,26 @@ func computeMigratedConfig(path string, content []byte, changes *ChangesSet) ([] } } + pathsToMove := []struct { + oldPath []string + newPath []string + }{ + { + []string{"keybinding", "worktrees", "viewWorktreeOptions"}, + []string{"keybinding", "universal", "newWorktree"}, + }, + } + + for _, pathToMove := range pathsToMove { + err, didMove := yaml_utils.MoveYamlKey(&rootNode, pathToMove.oldPath, pathToMove.newPath) + if err != nil { + return nil, false, fmt.Errorf("Couldn't migrate config file at `%s` for key %s: %w", path, strings.Join(pathToMove.oldPath, "."), err) + } + if didMove { + changes.Add(fmt.Sprintf("Moved '%s' to '%s'", strings.Join(pathToMove.oldPath, "."), strings.Join(pathToMove.newPath, "."))) + } + } + err = changeNullKeybindingsToDisabled(&rootNode, changes) if err != nil { return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err) @@ -449,7 +469,8 @@ func migrateAllBranchesLogCmd(rootNode *yaml.Node, changes *ChangesSet) error { // We will later populate it with the individual allBranchesLogCmd record cmdsKeyNode = &yaml.Node{Kind: yaml.ScalarNode, Value: "allBranchesLogCmds"} cmdsValueNode = &yaml.Node{Kind: yaml.SequenceNode, Content: []*yaml.Node{}} - gitNode.Content = append(gitNode.Content, + gitNode.Content = append( + gitNode.Content, cmdsKeyNode, cmdsValueNode, ) diff --git a/pkg/config/app_config_test.go b/pkg/config/app_config_test.go index 8e6c85f32..913bc47dc 100644 --- a/pkg/config/app_config_test.go +++ b/pkg/config/app_config_test.go @@ -78,6 +78,73 @@ keybinding: } } +func TestMigrationOfMovedKeys(t *testing.T) { + scenarios := []struct { + name string + input string + expected string + expectedDidChange bool + expectedChanges []string + }{ + { + name: "Empty String", + input: "", + expectedDidChange: false, + expectedChanges: []string{}, + }, + { + name: "No move needed", + input: `foo: + bar: 5 +`, + expectedDidChange: false, + expectedChanges: []string{}, + }, + { + name: "Move worktree keybinding into the universal section", + input: `keybinding: + universal: + quit: q + worktrees: + viewWorktreeOptions: w +`, + expected: `keybinding: + universal: + quit: q + newWorktree: w +`, + expectedDidChange: true, + expectedChanges: []string{"Moved 'keybinding.worktrees.viewWorktreeOptions' to 'keybinding.universal.newWorktree'"}, + }, + { + name: "Create the universal section if it doesn't exist", + input: `keybinding: + worktrees: + viewWorktreeOptions: w +`, + expected: `keybinding: + universal: + newWorktree: w +`, + expectedDidChange: true, + expectedChanges: []string{"Moved 'keybinding.worktrees.viewWorktreeOptions' to 'keybinding.universal.newWorktree'"}, + }, + } + + for _, s := range scenarios { + t.Run(s.name, func(t *testing.T) { + changes := NewChangesSet() + actual, didChange, err := computeMigratedConfig("path doesn't matter", []byte(s.input), changes) + assert.NoError(t, err) + assert.Equal(t, s.expectedDidChange, didChange) + if didChange { + assert.Equal(t, s.expected, string(actual)) + } + assert.Equal(t, s.expectedChanges, changes.ToSliceFromOldest()) + }) + } +} + func TestMigrateNullKeybindingsToDisabled(t *testing.T) { scenarios := []struct { name string diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index 844ef32bd..f83e26ea3 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -442,7 +442,6 @@ type KeybindingConfig struct { Status KeybindingStatusConfig `yaml:"status"` Files KeybindingFilesConfig `yaml:"files"` Branches KeybindingBranchesConfig `yaml:"branches"` - Worktrees KeybindingWorktreesConfig `yaml:"worktrees"` Commits KeybindingCommitsConfig `yaml:"commits"` AmendAttribute KeybindingAmendAttributeConfig `yaml:"amendAttribute"` Stash KeybindingStashConfig `yaml:"stash"` @@ -510,6 +509,7 @@ type KeybindingUniversalConfig struct { ConfirmInEditorAlt Keybinding `yaml:"confirmInEditor-alt"` Remove Keybinding `yaml:"remove"` New Keybinding `yaml:"new"` + NewWorktree Keybinding `yaml:"newWorktree"` Edit Keybinding `yaml:"edit"` OpenFile Keybinding `yaml:"openFile"` ScrollUpMain Keybinding `yaml:"scrollUpMain"` @@ -604,10 +604,6 @@ type KeybindingBranchesConfig struct { SortOrder Keybinding `yaml:"sortOrder"` } -type KeybindingWorktreesConfig struct { - ViewWorktreeOptions Keybinding `yaml:"viewWorktreeOptions"` -} - type KeybindingCommitsConfig struct { SquashDown Keybinding `yaml:"squashDown"` RenameCommit Keybinding `yaml:"renameCommit"` @@ -1035,6 +1031,7 @@ func GetDefaultConfigForPlatform(platform string) *UserConfig { ConfirmInEditorAlt: Keybinding{""}, Remove: Keybinding{"d"}, New: Keybinding{"n"}, + NewWorktree: Keybinding{"w"}, Edit: Keybinding{"e"}, OpenFile: Keybinding{"o"}, OpenRecentRepos: Keybinding{""}, @@ -1120,9 +1117,6 @@ func GetDefaultConfigForPlatform(platform string) *UserConfig { AddForkRemote: Keybinding{"F"}, SortOrder: Keybinding{"s"}, }, - Worktrees: KeybindingWorktreesConfig{ - ViewWorktreeOptions: Keybinding{"w"}, - }, Commits: KeybindingCommitsConfig{ SquashDown: Keybinding{"s"}, RenameCommit: Keybinding{"r"}, diff --git a/pkg/gui/controllers/basic_commits_controller.go b/pkg/gui/controllers/basic_commits_controller.go index f8b8d6783..410335712 100644 --- a/pkg/gui/controllers/basic_commits_controller.go +++ b/pkg/gui/controllers/basic_commits_controller.go @@ -90,7 +90,7 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [ Tooltip: self.c.Tr.MoveCommitsToNewBranchTooltip, }, { - Keys: opts.GetKeys(opts.Config.Worktrees.ViewWorktreeOptions), + Keys: opts.GetKeys(opts.Config.Universal.NewWorktree), Handler: self.withItem(self.c.Helpers().Worktree.NewWorktreeMenuForCommit), Description: self.c.Tr.NewWorktree, OpensMenu: true, diff --git a/pkg/gui/controllers/branches_controller.go b/pkg/gui/controllers/branches_controller.go index 7846febbf..27bef4b66 100644 --- a/pkg/gui/controllers/branches_controller.go +++ b/pkg/gui/controllers/branches_controller.go @@ -70,7 +70,7 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty Tooltip: self.c.Tr.MoveCommitsToNewBranchTooltip, }, { - Keys: opts.GetKeys(opts.Config.Worktrees.ViewWorktreeOptions), + Keys: opts.GetKeys(opts.Config.Universal.NewWorktree), Handler: self.withItem(self.c.Helpers().Worktree.NewWorktreeMenuForBranch), Description: self.c.Tr.NewWorktree, OpensMenu: true, diff --git a/pkg/gui/controllers/remote_branches_controller.go b/pkg/gui/controllers/remote_branches_controller.go index d232730c6..f70145d7b 100644 --- a/pkg/gui/controllers/remote_branches_controller.go +++ b/pkg/gui/controllers/remote_branches_controller.go @@ -49,7 +49,7 @@ func (self *RemoteBranchesController) GetKeybindings(opts types.KeybindingsOpts) Description: self.c.Tr.NewBranch, }, { - Keys: opts.GetKeys(opts.Config.Worktrees.ViewWorktreeOptions), + Keys: opts.GetKeys(opts.Config.Universal.NewWorktree), Handler: self.withItem(self.c.Helpers().Worktree.NewWorktreeMenuForRemoteBranch), Description: self.c.Tr.NewWorktree, OpensMenu: true, diff --git a/pkg/gui/controllers/stash_controller.go b/pkg/gui/controllers/stash_controller.go index 3f0bd9be2..06e6991c6 100644 --- a/pkg/gui/controllers/stash_controller.go +++ b/pkg/gui/controllers/stash_controller.go @@ -67,7 +67,7 @@ func (self *StashController) GetKeybindings(opts types.KeybindingsOpts) []*types Tooltip: self.c.Tr.NewBranchFromStashTooltip, }, { - Keys: opts.GetKeys(opts.Config.Worktrees.ViewWorktreeOptions), + Keys: opts.GetKeys(opts.Config.Universal.NewWorktree), Handler: self.withItem(self.c.Helpers().Worktree.NewWorktreeMenuForStash), Description: self.c.Tr.NewWorktree, OpensMenu: true, diff --git a/pkg/gui/controllers/tags_controller.go b/pkg/gui/controllers/tags_controller.go index fd61e239d..879a73628 100644 --- a/pkg/gui/controllers/tags_controller.go +++ b/pkg/gui/controllers/tags_controller.go @@ -54,7 +54,7 @@ func (self *TagsController) GetKeybindings(opts types.KeybindingsOpts) []*types. DisplayOnScreen: true, }, { - Keys: opts.GetKeys(opts.Config.Worktrees.ViewWorktreeOptions), + Keys: opts.GetKeys(opts.Config.Universal.NewWorktree), Handler: self.withItem(self.c.Helpers().Worktree.NewWorktreeMenuForTag), Description: self.c.Tr.NewWorktree, OpensMenu: true, diff --git a/pkg/integration/tests/demo/worktree_create_from_branches.go b/pkg/integration/tests/demo/worktree_create_from_branches.go index 817cb704b..64e0761cc 100644 --- a/pkg/integration/tests/demo/worktree_create_from_branches.go +++ b/pkg/integration/tests/demo/worktree_create_from_branches.go @@ -34,7 +34,7 @@ var WorktreeCreateFromBranches = NewIntegrationTest(NewIntegrationTestArgs{ Focus(). NavigateToLine(Contains("master")). Wait(500). - Press(keys.Worktrees.ViewWorktreeOptions). + Press(keys.Universal.NewWorktree). Tap(func() { t.Wait(500) diff --git a/pkg/integration/tests/worktree/add_for_existing_branch.go b/pkg/integration/tests/worktree/add_for_existing_branch.go index fb238bcda..7fbbc3fc8 100644 --- a/pkg/integration/tests/worktree/add_for_existing_branch.go +++ b/pkg/integration/tests/worktree/add_for_existing_branch.go @@ -26,7 +26,7 @@ var AddForExistingBranch = NewIntegrationTest(NewIntegrationTestArgs{ ). // the current branch is checked out by this worktree, so "Worktree // for 'mybranch'" is disabled - Press(keys.Worktrees.ViewWorktreeOptions). + Press(keys.Universal.NewWorktree). Tap(func() { t.ExpectPopup(). Menu(). @@ -41,7 +41,7 @@ var AddForExistingBranch = NewIntegrationTest(NewIntegrationTestArgs{ }). // otherbranch is not checked out anywhere, so we can make a worktree for it NavigateToLine(Contains("otherbranch")). - Press(keys.Worktrees.ViewWorktreeOptions). + Press(keys.Universal.NewWorktree). Tap(func() { t.ExpectPopup().Menu(). Title(Equals("New worktree")). diff --git a/pkg/integration/tests/worktree/add_from_branch.go b/pkg/integration/tests/worktree/add_from_branch.go index 87898c794..46dbc09f5 100644 --- a/pkg/integration/tests/worktree/add_from_branch.go +++ b/pkg/integration/tests/worktree/add_from_branch.go @@ -21,7 +21,7 @@ var AddFromBranch = NewIntegrationTest(NewIntegrationTestArgs{ Lines( Contains("mybranch"), ). - Press(keys.Worktrees.ViewWorktreeOptions). + Press(keys.Universal.NewWorktree). Tap(func() { t.ExpectPopup().Menu(). Title(Equals("New worktree")). diff --git a/pkg/integration/tests/worktree/add_from_branch_detached.go b/pkg/integration/tests/worktree/add_from_branch_detached.go index 6b74baff6..f17a50fef 100644 --- a/pkg/integration/tests/worktree/add_from_branch_detached.go +++ b/pkg/integration/tests/worktree/add_from_branch_detached.go @@ -21,7 +21,7 @@ var AddFromBranchDetached = NewIntegrationTest(NewIntegrationTestArgs{ Lines( Contains("mybranch"), ). - Press(keys.Worktrees.ViewWorktreeOptions). + Press(keys.Universal.NewWorktree). Tap(func() { t.ExpectPopup().Menu(). Title(Equals("New worktree")). diff --git a/pkg/integration/tests/worktree/add_from_commit.go b/pkg/integration/tests/worktree/add_from_commit.go index 484370ae3..21f26e4a2 100644 --- a/pkg/integration/tests/worktree/add_from_commit.go +++ b/pkg/integration/tests/worktree/add_from_commit.go @@ -24,7 +24,7 @@ var AddFromCommit = NewIntegrationTest(NewIntegrationTestArgs{ Contains("initial commit"), ). NavigateToLine(Contains("initial commit")). - Press(keys.Worktrees.ViewWorktreeOptions). + Press(keys.Universal.NewWorktree). Tap(func() { t.ExpectPopup().Menu(). Title(Equals("New worktree")). diff --git a/pkg/integration/tests/worktree/add_from_remote_branch.go b/pkg/integration/tests/worktree/add_from_remote_branch.go index 6dd4ea761..ac3d421fa 100644 --- a/pkg/integration/tests/worktree/add_from_remote_branch.go +++ b/pkg/integration/tests/worktree/add_from_remote_branch.go @@ -30,7 +30,7 @@ var AddFromRemoteBranch = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().RemoteBranches(). IsFocused(). NavigateToLine(Contains("feature")). - Press(keys.Worktrees.ViewWorktreeOptions). + Press(keys.Universal.NewWorktree). Tap(func() { t.ExpectPopup().Menu(). Title(Equals("New worktree")). diff --git a/pkg/integration/tests/worktree/add_from_stash.go b/pkg/integration/tests/worktree/add_from_stash.go index 26a3b97c4..c2541183c 100644 --- a/pkg/integration/tests/worktree/add_from_stash.go +++ b/pkg/integration/tests/worktree/add_from_stash.go @@ -23,7 +23,7 @@ var AddFromStash = NewIntegrationTest(NewIntegrationTestArgs{ Lines( Contains("my stash").IsSelected(), ). - Press(keys.Worktrees.ViewWorktreeOptions). + Press(keys.Universal.NewWorktree). Tap(func() { t.ExpectPopup().Menu(). Title(Equals("New worktree")). diff --git a/pkg/integration/tests/worktree/add_from_tag.go b/pkg/integration/tests/worktree/add_from_tag.go index 22ef2bb07..bab7c03d8 100644 --- a/pkg/integration/tests/worktree/add_from_tag.go +++ b/pkg/integration/tests/worktree/add_from_tag.go @@ -22,7 +22,7 @@ var AddFromTag = NewIntegrationTest(NewIntegrationTestArgs{ Lines( Contains("v1.0").IsSelected(), ). - Press(keys.Worktrees.ViewWorktreeOptions). + Press(keys.Universal.NewWorktree). Tap(func() { t.ExpectPopup().Menu(). Title(Equals("New worktree")). diff --git a/pkg/integration/tests/worktree/location_candidates.go b/pkg/integration/tests/worktree/location_candidates.go index 2160c6109..5fd835626 100644 --- a/pkg/integration/tests/worktree/location_candidates.go +++ b/pkg/integration/tests/worktree/location_candidates.go @@ -24,7 +24,7 @@ var LocationCandidates = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().Branches(). Focus(). NavigateToLine(Contains("mybranch")). - Press(keys.Worktrees.ViewWorktreeOptions). + Press(keys.Universal.NewWorktree). Tap(func() { t.ExpectPopup().Menu(). Title(Equals("New worktree")). diff --git a/schema-master/config.json b/schema-master/config.json index 9963aae61..5a0af4eb4 100644 --- a/schema-master/config.json +++ b/schema-master/config.json @@ -1658,9 +1658,6 @@ "branches": { "$ref": "#/$defs/KeybindingBranchesConfig" }, - "worktrees": { - "$ref": "#/$defs/KeybindingWorktreesConfig" - }, "commits": { "$ref": "#/$defs/KeybindingCommitsConfig" }, @@ -2881,6 +2878,20 @@ ], "default": "n" }, + "newWorktree": { + "oneOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "default": "w" + }, "edit": { "oneOf": [ { @@ -3407,26 +3418,6 @@ "additionalProperties": false, "type": "object" }, - "KeybindingWorktreesConfig": { - "properties": { - "viewWorktreeOptions": { - "oneOf": [ - { - "type": "string" - }, - { - "items": { - "type": "string" - }, - "type": "array" - } - ], - "default": "w" - } - }, - "additionalProperties": false, - "type": "object" - }, "LogConfig": { "properties": { "order": {