Show worktree name next to branch in branches list (#5340)

### PR Description

When a branch is checked out by another worktree, show the worktree name
in the label, e.g. "(worktree cosmos2)" instead of just "(worktree)", so
you can immediately see which worktree holds it.
This commit is contained in:
Stefan Haller 2026-03-09 11:58:28 +01:00 committed by GitHub
commit b05e33df89
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 55 additions and 31 deletions

View file

@ -57,7 +57,6 @@ func getBranchDisplayStrings(
showCommitHash := fullDescription || userConfig.Gui.ShowBranchCommitHash
branchStatus := BranchStatus(b, itemOperation, tr, now, userConfig)
divergence := divergenceStr(b, itemOperation, tr, userConfig)
worktreeIcon := lo.Ternary(icons.IsIconEnabled(), icons.LINKED_WORKTREE_ICON, fmt.Sprintf("(%s)", tr.LcWorktree))
// Recency is always three characters, plus one for the space
availableWidth := viewWidth - 4
@ -72,17 +71,42 @@ func getBranchDisplayStrings(
}
paddingNeededForDivergence := availableWidth
if checkedOutByWorkTree {
availableWidth -= utils.StringWidth(worktreeIcon) + 1
displayName := b.Name
if b.DisplayName != "" {
displayName = b.DisplayName
}
if len(branchStatus) > 0 {
availableWidth -= utils.StringWidth(utils.Decolorise(branchStatus)) + 1
}
displayName := b.Name
if b.DisplayName != "" {
displayName = b.DisplayName
worktreeIcon := ""
if checkedOutByWorkTree {
if wt, ok := git_commands.WorktreeForBranch(b, worktrees); ok && wt.Name != b.Name {
if icons.IsIconEnabled() {
worktreeIcon = fmt.Sprintf("(%s %s)", icons.LINKED_WORKTREE_ICON, wt.Name)
} else {
worktreeIcon = fmt.Sprintf("(%s %s)", tr.LcWorktree, wt.Name)
}
// If the worktree name doesn't fit in the available width, omit it
remaining := availableWidth - utils.StringWidth(worktreeIcon) - 1
if remaining < utils.StringWidth(displayName) {
if icons.IsIconEnabled() {
worktreeIcon = icons.LINKED_WORKTREE_ICON
} else {
worktreeIcon = fmt.Sprintf("(%s)", tr.LcWorktree)
}
}
} else {
if icons.IsIconEnabled() {
worktreeIcon = icons.LINKED_WORKTREE_ICON
} else {
worktreeIcon = fmt.Sprintf("(%s)", tr.LcWorktree)
}
}
availableWidth -= utils.StringWidth(worktreeIcon) + 1
}
nameTextStyle := GetBranchTextStyle(b.Name)

View file

@ -62,7 +62,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: true,
showDivergenceCfg: "none",
expected: []string{"1m", "branch_name (worktree)"},
expected: []string{"1m", "branch_name (worktree other-worktree)"},
},
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
@ -72,7 +72,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: true,
checkedOutByWorktree: true,
showDivergenceCfg: "none",
expected: []string{"1m", "󰘬", "branch_name 󰌹"},
expected: []string{"1m", "󰘬", "branch_name (󰌹 other-worktree)"},
},
{
branch: &models.Branch{
@ -104,7 +104,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: true,
showDivergenceCfg: "none",
expected: []string{"1m", "branch_name (worktree) ↓5↑3"},
expected: []string{"1m", "branch_name (worktree other-worktree) ↓5↑3"},
},
{
branch: &models.Branch{
@ -347,7 +347,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
worktrees := []*models.Worktree{}
if s.checkedOutByWorktree {
worktrees = append(worktrees, &models.Worktree{Branch: s.branch.Name})
worktrees = append(worktrees, &models.Worktree{Branch: s.branch.Name, Name: "other-worktree"})
}
t.Run(fmt.Sprintf("getBranchDisplayStrings_%d", i), func(t *testing.T) {

View file

@ -38,7 +38,7 @@ var FetchAndAutoForwardBranchesAllBranchesCheckedOutInOtherWorktree = NewIntegra
Contains("checked-out").IsSelected(),
Contains("diverged ↓2↑1"),
Contains("feature ↓2").DoesNotContain("↑"),
Contains("master (worktree) ↓1").DoesNotContain("↑"),
Contains("master (worktree linked-worktree) ↓1").DoesNotContain("↑"),
)
t.Views().Files().
@ -51,7 +51,7 @@ var FetchAndAutoForwardBranchesAllBranchesCheckedOutInOtherWorktree = NewIntegra
Contains("checked-out").IsSelected(),
Contains("diverged ↓2↑1"),
Contains("feature ✓"),
Contains("master (worktree) ↓1"),
Contains("master (worktree linked-worktree) ↓1"),
)
},
})

View file

@ -42,7 +42,7 @@ var AddFromBranch = NewIntegrationTest(NewIntegrationTestArgs{
IsFocused().
Lines(
Contains("newbranch").IsSelected(),
Contains("mybranch (worktree)"),
Contains("mybranch (worktree repo)"),
).
NavigateToLine(Contains("mybranch")).
Press(keys.Universal.Select).
@ -54,7 +54,7 @@ var AddFromBranch = NewIntegrationTest(NewIntegrationTestArgs{
}).
Lines(
Contains("mybranch").IsSelected(),
Contains("newbranch (worktree)"),
Contains("newbranch (worktree linked-worktree)"),
).
// Confirm the files view is still showing in the files window
Press(keys.Universal.PrevBlock)

View file

@ -37,7 +37,7 @@ var AddFromBranchDetached = NewIntegrationTest(NewIntegrationTestArgs{
IsFocused().
Lines(
Contains("(no branch)").IsSelected(),
Contains("mybranch (worktree)"),
Contains("mybranch (worktree repo)"),
)
t.Views().Status().

View file

@ -50,7 +50,7 @@ var AddFromCommit = NewIntegrationTest(NewIntegrationTestArgs{
IsFocused().
Lines(
Contains("newbranch").IsSelected(),
Contains("mybranch (worktree)"),
Contains("mybranch (worktree repo)"),
)
},
})

View file

@ -30,7 +30,7 @@ var AssociateBranchBisect = NewIntegrationTest(NewIntegrationTestArgs{
Focus().
Lines(
Contains("mybranch").IsSelected(),
Contains("newbranch (worktree)"),
Contains("newbranch (worktree linked-worktree)"),
)
// start a bisect on the main worktree
@ -70,7 +70,7 @@ var AssociateBranchBisect = NewIntegrationTest(NewIntegrationTestArgs{
}).
Lines(
Contains("newbranch").IsSelected(),
Contains("mybranch (worktree)"),
Contains("mybranch (worktree repo)"),
)
// switch back to main worktree

View file

@ -31,7 +31,7 @@ var AssociateBranchRebase = NewIntegrationTest(NewIntegrationTestArgs{
Focus().
Lines(
Contains("mybranch").IsSelected(),
Contains("newbranch (worktree)"),
Contains("newbranch (worktree linked-worktree)"),
)
// start a rebase on the main worktree
@ -57,7 +57,7 @@ var AssociateBranchRebase = NewIntegrationTest(NewIntegrationTestArgs{
}).
Lines(
Contains("newbranch").IsSelected(),
Contains("mybranch (worktree)"),
Contains("mybranch (worktree repo)"),
)
// start a rebase on the linked worktree
@ -83,7 +83,7 @@ var AssociateBranchRebase = NewIntegrationTest(NewIntegrationTestArgs{
Contains("(no branch").IsSelected(),
Contains("mybranch"),
// even though the linked worktree is rebasing, we still associate it with the branch
Contains("newbranch (worktree)"),
Contains("newbranch (worktree linked-worktree)"),
)
},
})

View file

@ -23,7 +23,7 @@ var DetachWorktreeFromBranch = NewIntegrationTest(NewIntegrationTestArgs{
Focus().
Lines(
Contains("mybranch").IsSelected(),
Contains("newbranch (worktree)"),
Contains("newbranch (worktree linked-worktree)"),
).
NavigateToLine(Contains("newbranch")).
Press(keys.Universal.Remove).

View file

@ -35,18 +35,18 @@ var FastForwardWorktreeBranch = NewIntegrationTest(NewIntegrationTestArgs{
Focus().
Lines(
Contains("mybranch").Contains("↓1").IsSelected(),
Contains("newbranch (worktree)").Contains("↓1"),
Contains("newbranch (worktree linked-worktree)").Contains("↓1"),
).
Press(keys.Branches.FastForward).
Lines(
Contains("mybranch").Contains("✓").IsSelected(),
Contains("newbranch (worktree)").Contains("↓1"),
Contains("newbranch (worktree linked-worktree)").Contains("↓1"),
).
NavigateToLine(Contains("newbranch (worktree)")).
NavigateToLine(Contains("newbranch (worktree linked-worktree)")).
Press(keys.Branches.FastForward).
Lines(
Contains("mybranch").Contains("✓"),
Contains("newbranch (worktree)").Contains("✓").IsSelected(),
Contains("newbranch (worktree linked-worktree)").Contains("✓").IsSelected(),
)
},
})

View file

@ -35,18 +35,18 @@ var FastForwardWorktreeBranchShouldNotPolluteCurrentWorktree = NewIntegrationTes
Focus().
Lines(
Contains("mybranch").Contains("↓1").IsSelected(),
Contains("newbranch (worktree)").Contains("↓1"),
Contains("newbranch (worktree linked-worktree)").Contains("↓1"),
).
Press(keys.Branches.FastForward).
Lines(
Contains("mybranch").Contains("✓").IsSelected(),
Contains("newbranch (worktree)").Contains("↓1"),
Contains("newbranch (worktree linked-worktree)").Contains("↓1"),
).
NavigateToLine(Contains("newbranch (worktree)")).
NavigateToLine(Contains("newbranch (worktree linked-worktree)")).
Press(keys.Branches.FastForward).
Lines(
Contains("mybranch").Contains("✓"),
Contains("newbranch (worktree)").Contains("✓").IsSelected(),
Contains("newbranch (worktree linked-worktree)").Contains("✓").IsSelected(),
).
NavigateToLine(Contains("mybranch"))

View file

@ -24,7 +24,7 @@ var RemoveWorktreeFromBranch = NewIntegrationTest(NewIntegrationTestArgs{
Focus().
Lines(
Contains("mybranch").IsSelected(),
Contains("newbranch (worktree)"),
Contains("newbranch (worktree linked-worktree)"),
).
NavigateToLine(Contains("newbranch")).
Press(keys.Universal.Remove).