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) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-06-29 17:47:25 +02:00
parent d53a9ea854
commit 737fb98967
19 changed files with 121 additions and 49 deletions

View file

@ -689,6 +689,7 @@ keybinding:
confirmInEditor: [<ctrl+enter>, <ctrl+s>]
remove: d
new: "n"
newWorktree: w
edit: e
openFile: o
scrollUpMain: [<pgup>, K, <ctrl+u>]
@ -769,8 +770,6 @@ keybinding:
fetchRemote: f
addForkRemote: F
sortOrder: s
worktrees:
viewWorktreeOptions: w
commits:
squashDown: s
renameCommit: r

View file

@ -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,
)

View file

@ -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

View file

@ -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{"<ctrl+s>"},
Remove: Keybinding{"d"},
New: Keybinding{"n"},
NewWorktree: Keybinding{"w"},
Edit: Keybinding{"e"},
OpenFile: Keybinding{"o"},
OpenRecentRepos: Keybinding{"<ctrl+r>"},
@ -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"},

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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)

View file

@ -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")).

View file

@ -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")).

View file

@ -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")).

View file

@ -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")).

View file

@ -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")).

View file

@ -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")).

View file

@ -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")).

View file

@ -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")).

View file

@ -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": {