Skip to content

Commit c23c652

Browse files
committed
Adds branch and worktree focus to Commit Graph
- Adds "Focus on Branch", "Focus on Worktree", and "Solo Branch" options to Commit Graph context menus - Allows focusing the graph and minimap on a specific branch or worktree by right-clicking them in the side bar or a graph row - Supports resolving working tree (WIP) rows to their corresponding branch so users can easily focus or solo the graph on that worktree's active branch - Cleanly reorganizes and groups related command contributions in the context menus Closes #5388
1 parent 1130acb commit c23c652

8 files changed

Lines changed: 528 additions & 369 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
1515
- Adds a working tree change count badge to the _Commit Graph_ view — mirrors the _Source Control_ view by showing the number of working tree changes on the GitLens panel tab, even while the panel is collapsed; controllable via the new `gitlens.graph.showWorkingTreeBadge` setting ([#5383](https://github.com/gitkraken/vscode-gitlens/issues/5383))
1616
- Adds an _Open in Integrated Terminal_ option for worktrees in the _Commit Graph_ and the _Worktrees_ view — opens the selected worktree's folder in the integrated terminal, matching the action already available for repositories and folders ([#5386](https://github.com/gitkraken/vscode-gitlens/issues/5386))
1717
- Adds a _Reveal in Explorer View_ option for working tree (WIP) files in the _Inspect_ and _Commit Graph_ views — right-click a staged, unstaged, or conflicted working file to reveal and select it in VS Code's Explorer view ([#5387](https://github.com/gitkraken/vscode-gitlens/issues/5387))
18+
- Adds _Focus on Branch_, _Focus on Worktree_, and _Solo Branch_ options to the _Commit Graph_ — right-click a branch or worktree in the side bar (or a graph row) to focus the graph and minimap on it, and right-click a working tree (WIP) row to focus or solo the graph on that worktree's branch ([#5388](https://github.com/gitkraken/vscode-gitlens/issues/5388))
1819

1920
### Changed
2021

contributions.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2605,6 +2605,35 @@
26052605
]
26062606
}
26072607
},
2608+
"gitlens.focusBranch:graph": {
2609+
"label": "Focus on Branch",
2610+
"menus": {
2611+
"webview/context": [
2612+
{
2613+
"when": "webviewItem =~ /gitlens:branch\\b/",
2614+
"group": "8_gitlens_actions",
2615+
"order": 7
2616+
}
2617+
]
2618+
}
2619+
},
2620+
"gitlens.focusWorktree:graph": {
2621+
"label": "Focus on Worktree",
2622+
"menus": {
2623+
"webview/context": [
2624+
{
2625+
"when": "webviewItem =~ /^gitlens:wip\\b/",
2626+
"group": "8_gitlens_actions",
2627+
"order": 7
2628+
},
2629+
{
2630+
"when": "webviewItem =~ /gitlens:worktree\\b(?=.*?\\b\\+branch\\b)/",
2631+
"group": "8_gitlens_actions",
2632+
"order": 7
2633+
}
2634+
]
2635+
}
2636+
},
26082637
"gitlens.getStarted": {
26092638
"label": "Get Started",
26102639
"commandPalette": "gitlens:walkthroughSupported",
@@ -4139,6 +4168,11 @@
41394168
"group": "8_gitlens_actions",
41404169
"order": 9
41414170
},
4171+
{
4172+
"when": "webviewItem =~ /^gitlens:wip\\b/",
4173+
"group": "8_gitlens_actions",
4174+
"order": 9
4175+
},
41424176
{
41434177
"when": "webviewItem =~ /gitlens:worktree\\b(?=.*?\\b\\+branch\\b)/",
41444178
"group": "8_gitlens_actions",

package.json

Lines changed: 391 additions & 355 deletions
Large diffs are not rendered by default.

src/constants.commands.generated.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ export type ContributedCommands =
133133
| 'gitlens.fetch:views'
134134
| 'gitlens.fetchRemote:graph'
135135
| 'gitlens.fetchRepositories'
136+
| 'gitlens.focusBranch:graph'
137+
| 'gitlens.focusWorktree:graph'
136138
| 'gitlens.getStarted'
137139
| 'gitlens.ghpr.views.openOrCreateWorktree'
138140
| 'gitlens.graph.addAuthor'

src/webviews/apps/plus/graph/graph-app.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -692,10 +692,17 @@ export class GraphApp extends SignalWatcher(LitElement) {
692692
action: GraphShowAction;
693693
target?: { sha: string; worktreePath: string; filePaths?: string[] };
694694
commitMessage?: string;
695+
scopeBranch?: { branchName: string; upstreamName?: string };
695696
}): Promise<void> {
696-
const { action, target, commitMessage } = pending;
697+
const { action, target, commitMessage, scopeBranch } = pending;
697698
if (action === 'scope-to-branch') {
698-
await this.scopeToBranch();
699+
// A target branch (from a Focus on Branch/Worktree command) scopes to it; otherwise scope
700+
// to the current branch (the welcome-page / generic `scope-to-branch` entry point).
701+
if (scopeBranch != null) {
702+
await this.scopeToBranchByName(scopeBranch.branchName, scopeBranch.upstreamName);
703+
} else {
704+
await this.scopeToBranch();
705+
}
699706
return;
700707
}
701708

@@ -2167,9 +2174,16 @@ export class GraphApp extends SignalWatcher(LitElement) {
21672174
});
21682175
}
21692176

2170-
private async handleScopeToBranchFromHeader(
2177+
private handleScopeToBranchFromHeader(
21712178
e: CustomEvent<{ branchName: string; upstreamName?: string }>,
21722179
): Promise<void> {
2180+
return this.scopeToBranchByName(e.detail.branchName, e.detail.upstreamName);
2181+
}
2182+
2183+
/** Focuses (scopes) the graph onto an arbitrary branch by name. Shared by the header popover, the
2184+
* sidebar/overview events, and the Focus on Branch/Worktree context-menu commands (via the
2185+
* `scope-to-branch` action). */
2186+
private async scopeToBranchByName(branchName: string, upstreamName?: string): Promise<void> {
21732187
// Use the selected repo's actual path (the opened workspace's path). That's what the host
21742188
// passes as `this.repository.path` when building the graph's row index AND the
21752189
// `wipMetadataBySha` branchRefs, so any scope/lookup branchRef constructed here must use
@@ -2179,8 +2193,6 @@ export class GraphApp extends SignalWatcher(LitElement) {
21792193
const repoPath = this.fallbackRepoPath;
21802194
if (repoPath == null) return;
21812195

2182-
const { branchName, upstreamName } = e.detail;
2183-
21842196
// Prefer the overview path so the merge target is resolved consistently with the overview card.
21852197
const overview = this.graphState.overview;
21862198
const branch =

src/webviews/apps/plus/graph/stateProvider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,6 +1221,7 @@ export class GraphStateProvider extends StateProviderBase<State['webviewId'], Ap
12211221
action: msg.params.action,
12221222
target: msg.params.target,
12231223
commitMessage: msg.params.commitMessage,
1224+
scopeBranch: msg.params.scopeBranch,
12241225
},
12251226
...(msg.params.action !== 'scope-to-branch' ? { details: { ...this.details, visible: true } } : {}),
12261227
});

src/webviews/plus/graph/graphWebview.ts

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ import type {
381381
GraphRefType,
382382
GraphRemoteContextValue,
383383
GraphRepository,
384+
GraphScopeBranch,
384385
GraphScrollMarkerTypes,
385386
GraphSearchResults,
386387
GraphSelectedRows,
@@ -10274,20 +10275,44 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
1027410275
@command('gitlens.graph.soloBranch')
1027510276
@command('gitlens.graph.soloTag')
1027610277
@debug()
10277-
private soloReference(item?: GraphItemContext) {
10278-
if (!isGraphItemRefContext(item)) return;
10278+
private soloReference(item?: GraphItemContext): Promise<void> {
10279+
// Branch/tag/worktree leaves & rows carry a real ref with an id. WIP rows carry an
10280+
// uncommitted revision (no id) — fall through to resolve the worktree's branch.
10281+
if (isGraphItemRefContext(item)) {
10282+
const { ref } = item.webviewItemValue;
10283+
if (ref.id != null) {
10284+
this.soloByName(ref.repoPath, ref.name);
10285+
return Promise.resolve();
10286+
}
10287+
}
1027910288

10280-
const { ref } = item.webviewItemValue;
10281-
if (ref.id == null) return;
10289+
return this.soloWipReference(item);
10290+
}
10291+
10292+
/** Solo the WIP row's worktree onto its current branch. The WIP context carries only an
10293+
* uncommitted revision + `worktreePath`, so resolve that worktree's branch and filter the
10294+
* graph (on its own repo) to it. */
10295+
private async soloWipReference(item?: GraphItemContext): Promise<void> {
10296+
if (!isGraphItemRefContext(item, 'revision')) return;
1028210297

10283-
const repo = this.container.git.getRepository(ref.repoPath);
10284-
if (repo == null) return Promise.resolve();
10298+
const { worktreePath } = item.webviewItemValue;
10299+
if (worktreePath == null) return;
10300+
10301+
const branch = await this.container.git.getRepositoryService(worktreePath).branches.getBranch();
10302+
if (branch == null) return;
10303+
10304+
this.soloByName(this.repository?.path ?? worktreePath, branch.name);
10305+
}
10306+
10307+
private soloByName(repoPath: string, name: string): void {
10308+
const repo = this.container.git.getRepository(repoPath);
10309+
if (repo == null) return;
1028510310

1028610311
// Show the graph with a ref: search query to filter the graph to this branch
10287-
return void executeCommand<ShowInCommitGraphCommandArgs>('gitlens.showInCommitGraph', {
10312+
void executeCommand<ShowInCommitGraphCommandArgs>('gitlens.showInCommitGraph', {
1028810313
repository: repo,
1028910314
search: {
10290-
query: `ref:${ref.name}`,
10315+
query: `ref:${name}`,
1029110316
filter: true,
1029210317
matchAll: false,
1029310318
matchCase: false,
@@ -10297,6 +10322,39 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
1029710322
});
1029810323
}
1029910324

10325+
// Two command ids, one handler — VS Code menu titles are static, so distinct ids let the menu
10326+
// read "Focus on Branch" on branch rows/leaves and "Focus on Worktree" on worktree/WIP rows.
10327+
@command('gitlens.focusBranch:graph')
10328+
@command('gitlens.focusWorktree:graph')
10329+
@debug()
10330+
private async focusReference(item?: GraphItemContext): Promise<void> {
10331+
const scopeBranch = await this.getScopeBranch(item);
10332+
if (scopeBranch == null) return;
10333+
10334+
// Invoked from a context menu inside the open graph (warm), so notify the webview directly to
10335+
// focus (scope) onto the branch — mirrors the `scope-to-branch` action the popover/overview use.
10336+
void this.host.notify(DidRequestGraphActionNotification, {
10337+
action: 'scope-to-branch',
10338+
scopeBranch: scopeBranch,
10339+
});
10340+
}
10341+
10342+
/** Resolves the branch to focus from a Focus context item. Branch leaves/rows and worktree
10343+
* leaves carry a branch ref directly; WIP rows carry only `worktreePath`, so resolve its
10344+
* current branch. */
10345+
private async getScopeBranch(item?: GraphItemContext): Promise<GraphScopeBranch | undefined> {
10346+
const ref = this.getGraphItemRef(item, 'branch');
10347+
if (ref != null) return { branchName: ref.name, upstreamName: ref.upstream?.name };
10348+
10349+
if (!isGraphItemRefContext(item, 'revision')) return undefined;
10350+
10351+
const { worktreePath } = item.webviewItemValue;
10352+
if (worktreePath == null) return undefined;
10353+
10354+
const branch = await this.container.git.getRepositoryService(worktreePath).branches.getBranch();
10355+
return branch != null ? { branchName: branch.name, upstreamName: branch.upstream?.name } : undefined;
10356+
}
10357+
1030010358
@command('gitlens.switchToAnotherBranch:graph')
1030110359
@debug()
1030210360
private switchToAnother(item?: GraphItemContext | unknown) {

src/webviews/plus/graph/protocol.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,14 @@ export interface GraphActionTarget {
193193
* entry points). Omitted means "resolve all conflicts". Ignored by other actions. */
194194
filePaths?: string[];
195195
}
196+
197+
/** Target branch for a `scope-to-branch` action. When present, the webview focuses (scopes) the
198+
* graph to this branch instead of the current branch — used by the Focus on Branch/Worktree
199+
* context-menu commands. */
200+
export interface GraphScopeBranch {
201+
branchName: string;
202+
upstreamName?: string;
203+
}
196204
/** Sub-visualization shown when `displayMode === 'visualizations'`.
197205
* Adding a new visualization is a 4-step extension: extend this union, render its component in
198206
* `gl-graph-visualizations`, persist any per-visualization config in `graph-app.persistStateNow`,
@@ -326,7 +334,12 @@ export interface State extends WebviewState<'gitlens.graph' | 'gitlens.views.gra
326334
visible?: boolean;
327335
position?: number;
328336
};
329-
pendingAction?: { action: GraphShowAction; target?: GraphActionTarget; commitMessage?: string };
337+
pendingAction?: {
338+
action: GraphShowAction;
339+
target?: GraphActionTarget;
340+
commitMessage?: string;
341+
scopeBranch?: GraphScopeBranch;
342+
};
330343
/** Per-worktree commit drafts for this repo's WIP rows, keyed by worktree fsPath (== `repoPath`
331344
* for the primary WIP, == the secondary worktree's fsPath for each secondary WIP row).
332345
* Restored on WIP row selection; mutated via {@link UpdateWipDraftCommand}. */
@@ -1148,6 +1161,8 @@ export interface DidRequestGraphActionParams {
11481161
/** Optional seed value for the WIP details panel's commit message input. Currently used after
11491162
* Undo Commit to restore the undone commit's message into the box where the user will redo it. */
11501163
commitMessage?: string;
1164+
/** For `scope-to-branch`: the branch to focus the graph on. Absent = focus the current branch. */
1165+
scopeBranch?: GraphScopeBranch;
11511166
}
11521167
export const DidRequestGraphActionNotification = new IpcNotification<DidRequestGraphActionParams>(
11531168
scope,

0 commit comments

Comments
 (0)