Skip to content

Commit 3cd4c6e

Browse files
committed
Allow switching to other worktrees by switching branches
The upstream worktrees implementation allows automatically switching to another worktree by switching to the branch that was checked out in the worktree. We had custom logic to check for these branches and disable them, which was carried over from the original implementation. This commit removes those checks and bring us closer to the upstream both in the implementation code and in the user-facing behavior
1 parent 58d201c commit 3cd4c6e

13 files changed

Lines changed: 31 additions & 124 deletions

File tree

app/src/ui/branches/branch-list-item.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,6 @@ interface IBranchListItemProps {
2828

2929
readonly authorDate: Date | undefined
3030

31-
/** Name of the worktree where this branch is checked out, if any */
32-
readonly worktreeName: string | null
33-
3431
/** When a drag element has landed on a branch that is not current */
3532
readonly onDropOntoBranch?: (branchName: string) => void
3633

@@ -98,10 +95,7 @@ export class BranchListItem extends React.Component<
9895
}
9996

10097
public render() {
101-
const { authorDate, isCurrentBranch, isLocalOnly, name, worktreeName } =
102-
this.props
103-
104-
const isInUseByOtherWorktree = worktreeName !== null && !isCurrentBranch
98+
const { authorDate, isCurrentBranch, name, isLocalOnly } = this.props
10599

106100
function getIcon() {
107101
if (isLocalOnly) {
@@ -112,7 +106,6 @@ export class BranchListItem extends React.Component<
112106
const className = classNames('branches-list-item', {
113107
'drop-target': this.state.isDragInProgress,
114108
'local-only': isLocalOnly,
115-
disabled: isInUseByOtherWorktree,
116109
})
117110

118111
return (

app/src/ui/branches/branch-list.tsx

Lines changed: 9 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import * as React from 'react'
2-
import * as Path from 'path'
32

43
import { Branch } from '../../models/branch'
5-
import { WorktreeEntry } from '../../models/worktree'
64

75
import { assertNever } from '../../lib/fatal-error'
86

@@ -54,11 +52,6 @@ interface IBranchListProps {
5452
*/
5553
readonly recentBranches: ReadonlyArray<Branch>
5654

57-
/**
58-
* All worktrees in the repository.
59-
*/
60-
readonly allWorktrees: ReadonlyArray<WorktreeEntry>
61-
6255
/**
6356
* The sort order for branch lists in the current user preferences.
6457
*/
@@ -195,9 +188,9 @@ export class BranchList extends React.Component<IBranchListProps> {
195188
private get groups() {
196189
return this.getGroups(
197190
this.props.defaultBranch,
191+
this.props.currentBranch,
198192
this.props.allBranches,
199193
this.props.recentBranches,
200-
this.props.allWorktrees,
201194
this.props.branchSortOrder
202195
)
203196
}
@@ -311,20 +304,17 @@ export class BranchList extends React.Component<IBranchListProps> {
311304
): JSX.Element | string | null => {
312305
const { tip, name } = item.branch
313306

314-
const absoluteDate = formatDate(tip.author.date, {
315-
dateStyle: 'full',
316-
timeStyle: 'short',
317-
})
307+
const authorDate = tip.author.date
308+
309+
const absoluteDate = authorDate
310+
? formatDate(authorDate, {
311+
dateStyle: 'full',
312+
timeStyle: 'short',
313+
})
314+
: null
318315

319-
const otherWorktreeName = this.inUseByOtherWorktreeName(item)
320316
return (
321317
<div className="branches-list-item-tooltip list-item-tooltip">
322-
{otherWorktreeName && (
323-
<div className="label tooltip-warning">
324-
This branch cannot be checked out because it is in use by worktree
325-
&quot;{otherWorktreeName}&quot;
326-
</div>
327-
)}
328318
<div>
329319
<div className="label">Full Name: </div>
330320
{name}
@@ -339,17 +329,6 @@ export class BranchList extends React.Component<IBranchListProps> {
339329
)
340330
}
341331

342-
private inUseByOtherWorktreeName(item: IBranchListItem): string | null {
343-
const worktreeName = item.worktreeInUse
344-
? Path.basename(item.worktreeInUse.path)
345-
: null
346-
347-
return worktreeName !== null &&
348-
this.props.currentBranch?.name !== item.branch.name
349-
? worktreeName
350-
: null
351-
}
352-
353332
private parseHeader(label: string): BranchGroupIdentifier | null {
354333
switch (label) {
355334
case 'default':
@@ -415,16 +394,6 @@ export class BranchList extends React.Component<IBranchListProps> {
415394
}
416395

417396
private onItemClick = (item: IBranchListItem, source: ClickSource) => {
418-
// Don't allow clicking branches that are in use by other worktrees
419-
if (item.worktreeInUse !== null) {
420-
const currentBranch = this.props.currentBranch
421-
const isCurrentBranch =
422-
currentBranch !== null && currentBranch.name === item.branch.name
423-
if (!isCurrentBranch) {
424-
return
425-
}
426-
}
427-
428397
if (this.props.onItemClick) {
429398
this.props.onItemClick(item.branch, source)
430399
}
@@ -434,18 +403,6 @@ export class BranchList extends React.Component<IBranchListProps> {
434403
selectedItem: IBranchListItem | null,
435404
source: SelectionSource
436405
) => {
437-
// Don't allow selecting branches that are in use by other worktrees
438-
if (selectedItem?.worktreeInUse !== null) {
439-
const currentBranch = this.props.currentBranch
440-
const isCurrentBranch =
441-
currentBranch !== null &&
442-
selectedItem !== null &&
443-
currentBranch.name === selectedItem.branch.name
444-
if (!isCurrentBranch) {
445-
return
446-
}
447-
}
448-
449406
if (this.props.onSelectionChanged) {
450407
this.props.onSelectionChanged(
451408
selectedItem ? selectedItem.branch : null,

app/src/ui/branches/branch-renderer.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import * as React from 'react'
2-
import * as Path from 'path'
32

43
import { Branch, BranchType } from '../../models/branch'
54

@@ -20,19 +19,13 @@ export function renderDefaultBranch(
2019
const currentBranchName = currentBranch ? currentBranch.name : null
2120
const isLocalOnly =
2221
branch.type === BranchType.Local && (!branch.upstream || branch.isGone)
23-
24-
const worktreeName = item.worktreeInUse
25-
? Path.basename(item.worktreeInUse.path)
26-
: null
27-
2822
return (
2923
<BranchListItem
3024
name={branch.name}
3125
isCurrentBranch={branch.name === currentBranchName}
3226
authorDate={branch.tip.author.date}
3327
isLocalOnly={isLocalOnly}
3428
matches={matches}
35-
worktreeName={worktreeName}
3629
onDropOntoBranch={onDropOntoBranch}
3730
onDropOntoCurrentBranch={onDropOntoCurrentBranch}
3831
/>

app/src/ui/branches/branch-select.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ export class BranchSelect extends React.Component<
123123
defaultBranch={defaultBranch}
124124
branchSortOrder={this.props.branchSortOrder}
125125
recentBranches={recentBranches}
126-
allWorktrees={[]}
127126
filterText={filterText}
128127
onFilterTextChanged={this.onFilterTextChanged}
129128
selectedBranch={selectedBranch}

app/src/ui/branches/branches-container.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
import { Branch } from '../../models/branch'
99
import { BranchesTab } from '../../models/branches-tab'
1010
import { PopupType } from '../../models/popup'
11-
import { WorktreeEntry } from '../../models/worktree'
1211

1312
import { Dispatcher } from '../dispatcher'
1413
import { FoldoutType } from '../../lib/app-state'
@@ -63,8 +62,6 @@ interface IBranchesContainerProps {
6362

6463
readonly branchSortOrder: BranchSortOrder
6564

66-
readonly allWorktrees: ReadonlyArray<WorktreeEntry>
67-
6865
/** The pull request associated with the current branch. */
6966
readonly currentPullRequest: PullRequest | null
7067

@@ -282,7 +279,6 @@ export class BranchesContainer extends React.Component<
282279
allBranches={this.props.allBranches}
283280
recentBranches={this.props.recentBranches}
284281
branchSortOrder={this.props.branchSortOrder}
285-
allWorktrees={this.props.allWorktrees}
286282
onItemClick={this.onBranchItemClick}
287283
filterText={this.state.branchFilterText}
288284
onFilterTextChanged={this.onBranchFilterTextChanged}

app/src/ui/branches/group-branches.ts

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Branch, BranchType } from '../../models/branch'
22
import { BranchSortOrder } from '../../models/branch-sort-order'
3-
import { WorktreeEntry } from '../../models/worktree'
43
import { IFilterListGroup, IFilterListItem } from '../lib/filter-list'
54

65
export type BranchGroupIdentifier = 'default' | 'recent' | 'other'
@@ -9,8 +8,6 @@ export interface IBranchListItem extends IFilterListItem {
98
readonly text: ReadonlyArray<string>
109
readonly id: string
1110
readonly branch: Branch
12-
/** The worktree where this branch is currently checked out, if any */
13-
readonly worktreeInUse: WorktreeEntry | null
1411
}
1512

1613
/**
@@ -21,49 +18,23 @@ export function isLocalOnlyBranch(branch: Branch): boolean {
2118
return branch.type === BranchType.Local && (!branch.upstream || branch.isGone)
2219
}
2320

24-
/**
25-
* Finds the worktree where a given branch is currently checked out.
26-
* Returns null if the branch is not checked out in any worktree.
27-
*/
28-
export function findWorktreeForBranch(
29-
branchName: string,
30-
worktrees: ReadonlyArray<WorktreeEntry>
31-
): WorktreeEntry | null {
32-
for (const worktree of worktrees) {
33-
if (worktree.branch === null) {
34-
continue
35-
}
36-
// Extract branch name from refs/heads/branch-name format
37-
const wtBranchName = worktree.branch.replace(/^refs\/heads\//, '')
38-
if (wtBranchName === branchName) {
39-
return worktree
40-
}
41-
}
42-
return null
43-
}
44-
4521
export function groupBranches(
4622
defaultBranch: Branch | null,
23+
currentBranch: Branch | null,
4724
allBranches: ReadonlyArray<Branch>,
4825
recentBranches: ReadonlyArray<Branch>,
49-
allWorktrees: ReadonlyArray<WorktreeEntry>,
5026
sortOrder: BranchSortOrder
5127
): ReadonlyArray<IFilterListGroup<IBranchListItem>> {
5228
const groups = new Array<IFilterListGroup<IBranchListItem>>()
5329

5430
if (defaultBranch) {
55-
const worktreeInUse = findWorktreeForBranch(
56-
defaultBranch.name,
57-
allWorktrees
58-
)
5931
groups.push({
6032
identifier: 'default',
6133
items: [
6234
{
6335
text: [defaultBranch.name],
6436
id: defaultBranch.name,
6537
branch: defaultBranch,
66-
worktreeInUse,
6738
},
6839
],
6940
})
@@ -78,12 +49,10 @@ export function groupBranches(
7849
const recentBranches = new Array<IBranchListItem>()
7950

8051
for (const branch of recentBranchesWithoutDefault) {
81-
const worktreeInUse = findWorktreeForBranch(branch.name, allWorktrees)
8252
recentBranches.push({
8353
text: [branch.name],
8454
id: branch.name,
8555
branch,
86-
worktreeInUse,
8756
})
8857
recentBranchNames.add(branch.name)
8958
}
@@ -107,12 +76,10 @@ export function groupBranches(
10776
)
10877

10978
const remainingItems = sortedRemainingBranches.map(b => {
110-
const worktreeInUse = findWorktreeForBranch(b.name, allWorktrees)
11179
return {
11280
text: [b.name],
11381
id: b.name,
11482
branch: b,
115-
worktreeInUse,
11683
}
11784
})
11885
groups.push({

app/src/ui/history/compare.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,6 @@ export class CompareSidebar extends React.Component<
423423
currentBranch={this.props.currentBranch}
424424
allBranches={branches}
425425
recentBranches={recentBranches}
426-
allWorktrees={[]}
427426
branchSortOrder={this.props.branchSortOrder}
428427
filterText={filterText}
429428
textbox={this.textbox!}

app/src/ui/multi-commit-operation/choose-branch/base-choose-branch-dialog.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,6 @@ export class ChooseBranchDialog extends React.Component<
240240
currentBranch={currentBranch}
241241
defaultBranch={this.props.defaultBranch}
242242
recentBranches={this.props.recentBranches}
243-
allWorktrees={[]}
244243
branchSortOrder={this.props.branchSortOrder}
245244
filterText={this.state.filterText}
246245
onFilterTextChanged={this.onFilterTextChanged}

app/src/ui/multi-commit-operation/choose-branch/choose-target-branch.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,6 @@ export class ChooseTargetBranchDialog extends React.Component<
194194
currentBranch={this.props.currentBranch}
195195
defaultBranch={this.props.defaultBranch}
196196
recentBranches={this.props.recentBranches}
197-
allWorktrees={[]}
198197
branchSortOrder={this.props.branchSortOrder}
199198
filterText={this.state.filterText}
200199
onFilterTextChanged={this.onFilterTextChanged}

app/src/ui/toolbar/branch-dropdown.tsx

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,12 @@ import { TooltipTarget } from '../lib/tooltip'
2727
import { BranchType, Branch } from '../../models/branch'
2828
import { PopupType } from '../../models/popup'
2929
import { generateBranchContextMenuItems } from '../branches/branch-list-item-context-menu'
30-
import {
31-
findWorktreeForBranch,
32-
isLocalOnlyBranch,
33-
} from '../branches/group-branches'
30+
import { isLocalOnlyBranch } from '../branches/group-branches'
3431
import { showContextualMenu } from '../../lib/menu-item'
3532
import { Emoji } from '../../lib/emoji'
3633
import { BranchSortOrder } from '../../models/branch-sort-order'
3734
import { enableResizingToolbarButtons } from '../../lib/feature-flag'
35+
import { WorktreeEntry } from '../../models/worktree'
3836

3937
interface IBranchDropdownProps {
4038
readonly dispatcher: Dispatcher
@@ -111,7 +109,6 @@ export class BranchDropdown extends React.Component<IBranchDropdownProps> {
111109
recentBranches={branchesState.recentBranches}
112110
currentBranch={currentBranch}
113111
defaultBranch={branchesState.defaultBranch}
114-
allWorktrees={repositoryState.worktrees}
115112
dispatcher={this.props.dispatcher}
116113
repository={this.props.repository}
117114
selectedTab={this.props.selectedTab}
@@ -456,7 +453,7 @@ export class BranchDropdown extends React.Component<IBranchDropdownProps> {
456453
const branches = allBranches.filter(
457454
branch =>
458455
isLocalOnlyBranch(branch) &&
459-
findWorktreeForBranch(branch.name, worktrees) === null
456+
this.findWorktreeForBranch(branch.name, worktrees) === null
460457
)
461458

462459
if (branches.length === 0) {
@@ -470,6 +467,22 @@ export class BranchDropdown extends React.Component<IBranchDropdownProps> {
470467
})
471468
}
472469

470+
private findWorktreeForBranch(
471+
branchName: string,
472+
worktrees: ReadonlyArray<WorktreeEntry>
473+
): WorktreeEntry | null {
474+
for (const worktree of worktrees) {
475+
if (worktree.branch === null) {
476+
continue
477+
}
478+
const wtBranchName = worktree.branch.replace(/^refs\/heads\//, '')
479+
if (wtBranchName === branchName) {
480+
return worktree
481+
}
482+
}
483+
return null
484+
}
485+
473486
private onCheckoutInNewWorktree = (branch: Branch) => {
474487
this.props.dispatcher.closeFoldout(FoldoutType.Branch)
475488
this.props.dispatcher.showPopup({

0 commit comments

Comments
 (0)