Skip to content

Commit 6bcd272

Browse files
committed
Adds a jump to ref button to the graph
1 parent c6e5aa7 commit 6bcd272

File tree

9 files changed

+95
-7
lines changed

9 files changed

+95
-7
lines changed

src/git/models/reference.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export interface GitBranchReference {
104104
id?: string;
105105
name: string;
106106
ref: string;
107+
sha?: string;
107108
readonly remote: boolean;
108109
readonly upstream?: { name: string; missing: boolean };
109110
repoPath: string;
@@ -114,6 +115,7 @@ export interface GitRevisionReference {
114115
id?: undefined;
115116
name: string;
116117
ref: string;
118+
sha: string;
117119
repoPath: string;
118120

119121
number?: string | undefined;
@@ -125,6 +127,7 @@ export interface GitStashReference {
125127
id?: undefined;
126128
name: string;
127129
ref: string;
130+
sha: string;
128131
repoPath: string;
129132
number: string;
130133

@@ -137,6 +140,7 @@ export interface GitTagReference {
137140
id?: string;
138141
name: string;
139142
ref: string;
143+
sha?: string;
140144
repoPath: string;
141145
}
142146

@@ -199,6 +203,7 @@ export function createReference(
199203
refType: 'stash',
200204
repoPath: repoPath,
201205
ref: ref,
206+
sha: ref,
202207
name: options.name,
203208
number: options.number,
204209
message: options.message,
@@ -217,6 +222,7 @@ export function createReference(
217222
refType: 'revision',
218223
repoPath: repoPath,
219224
ref: ref,
225+
sha: ref,
220226
name: options.name ?? shortenRevision(ref, { force: true, strings: { working: 'Working Tree' } }),
221227
message: options.message,
222228
};

src/plus/webviews/graph/graphWebview.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import {
7070
} from '../../../git/models/repository';
7171
import type { GitSearch } from '../../../git/search';
7272
import { getSearchQueryComparisonKey } from '../../../git/search';
73+
import { ReferencesQuickPickIncludes, showReferencePicker } from '../../../quickpicks/referencePicker';
7374
import { showRepositoryPicker } from '../../../quickpicks/repositoryPicker';
7475
import { executeActionCommand, executeCommand, executeCoreCommand, registerCommand } from '../../../system/command';
7576
import { configuration } from '../../../system/configuration';
@@ -141,6 +142,7 @@ import type {
141142
UpdateSelectionParams,
142143
} from './protocol';
143144
import {
145+
ChooseRefRequest,
144146
ChooseRepositoryCommand,
145147
DidChangeAvatarsNotification,
146148
DidChangeColumnsNotification,
@@ -628,6 +630,9 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
628630
case ChooseRepositoryCommand.is(e):
629631
void this.onChooseRepository();
630632
break;
633+
case ChooseRefRequest.is(e):
634+
void this.onChooseRef(ChooseRefRequest, e);
635+
break;
631636
case DoubleClickedCommandType.is(e):
632637
void this.onDoubleClick(e.params);
633638
break;
@@ -1410,6 +1415,33 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
14101415
this.repository = pick;
14111416
}
14121417

1418+
async onChooseRef<T extends typeof ChooseRefRequest>(requestType: T, msg: IpcCallMessageType<T>) {
1419+
if (this.repository == null) {
1420+
return this.host.respond(requestType, msg, undefined);
1421+
}
1422+
1423+
let pick;
1424+
// If not alt, then jump directly to HEAD
1425+
if (!msg.params.alt) {
1426+
let branch = find(this._graph!.branches.values(), b => b.current);
1427+
if (branch == null) {
1428+
branch = await this.repository.getBranch();
1429+
}
1430+
if (branch != null) {
1431+
pick = branch;
1432+
}
1433+
} else {
1434+
pick = await showReferencePicker(
1435+
this.repository.path,
1436+
`Jump to Reference ${GlyphChars.Dot} ${this.repository?.name}`,
1437+
'Choose a reference to jump to',
1438+
{ include: ReferencesQuickPickIncludes.BranchesAndTags },
1439+
);
1440+
}
1441+
1442+
return this.host.respond(requestType, msg, pick?.sha != null ? { name: pick.name, sha: pick.sha } : undefined);
1443+
}
1444+
14131445
private _fireSelectionChangedDebounced: Deferrable<GraphWebviewProvider['fireSelectionChanged']> | undefined =
14141446
undefined;
14151447

src/plus/webviews/graph/protocol.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,12 @@ export type UpdateStateCallback = (
219219

220220
export const ChooseRepositoryCommand = new IpcCommand(scope, 'chooseRepository');
221221

222+
export interface ChooseRefParams {
223+
alt: boolean;
224+
}
225+
export type DidChooseRefParams = { name: string; sha: string } | undefined;
226+
export const ChooseRefRequest = new IpcRequest<ChooseRefParams, DidChooseRefParams>(scope, 'chooseRef');
227+
222228
export type DoubleClickedParams =
223229
| {
224230
type: 'ref';

src/webviews/apps/plus/graph/GraphWrapper.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type {
1414
} from '@gitkraken/gitkraken-components';
1515
import GraphContainer, { CommitDateTimeSources, refZone } from '@gitkraken/gitkraken-components';
1616
import { VSCodeCheckbox, VSCodeRadio, VSCodeRadioGroup } from '@vscode/webview-ui-toolkit/react';
17-
import type { FormEvent, ReactElement } from 'react';
17+
import type { FormEvent, MouseEvent, ReactElement } from 'react';
1818
import React, { createElement, useEffect, useMemo, useRef, useState } from 'react';
1919
import { getPlatform } from '@env/platform';
2020
import type { DateStyle } from '../../../../config';
@@ -62,6 +62,8 @@ import { pluralize } from '../../../../system/string';
6262
import { createWebviewCommandLink } from '../../../../system/webview';
6363
import type { IpcNotification } from '../../../protocol';
6464
import { DidChangeHostWindowFocusNotification } from '../../../protocol';
65+
import { GlButton } from '../../shared/components/button.react';
66+
import { CodeIcon } from '../../shared/components/code-icon.react';
6567
import { MenuDivider, MenuItem, MenuLabel, MenuList } from '../../shared/components/menu/react';
6668
import { PopMenu } from '../../shared/components/overlays/pop-menu/react';
6769
import { GlPopover } from '../../shared/components/overlays/popover.react';
@@ -86,6 +88,7 @@ export interface GraphWrapperProps {
8688
onDoubleClickRef?: (ref: GraphRef, metadata?: GraphRefMetadataItem) => void;
8789
onDoubleClickRow?: (row: GraphRow, preserveFocus?: boolean) => void;
8890
onHoverRowPromise?: (row: GraphRow) => Promise<DidGetRowHoverParams | undefined>;
91+
onJumpToRefPromise?: (alt: boolean) => Promise<{ name: string; sha: string } | undefined>;
8992
onMissingAvatars?: (emails: Record<string, string>) => void;
9093
onMissingRefsMetadata?: (metadata: GraphMissingRefsMetadata) => void;
9194
onMoreRows?: (id?: string) => void;
@@ -210,6 +213,7 @@ export function GraphWrapper({
210213
onDoubleClickRow,
211214
onEnsureRowPromise,
212215
onHoverRowPromise,
216+
onJumpToRefPromise,
213217
onMissingAvatars,
214218
onMissingRefsMetadata,
215219
onMoreRows,
@@ -702,6 +706,16 @@ export function GraphWrapper({
702706
onChooseRepository?.();
703707
};
704708

709+
const handleJumpToRef = async (e: MouseEvent) => {
710+
const ref = await onJumpToRefPromise?.(e.altKey);
711+
if (ref != null) {
712+
const sha = await ensureSearchResultRow(ref.sha);
713+
if (sha == null) return;
714+
715+
queueMicrotask(() => graphRef.current?.selectCommits([sha], false, true));
716+
}
717+
};
718+
705719
const handleFilterChange = (e: Event | FormEvent<HTMLElement>) => {
706720
const $el = e.target as HTMLInputElement;
707721

@@ -1202,6 +1216,14 @@ export function GraphWrapper({
12021216
</span>
12031217
</div>
12041218
</GlPopover>
1219+
<GlButton appearance="toolbar" onClick={handleJumpToRef}>
1220+
<CodeIcon icon="target"></CodeIcon>
1221+
<span slot="tooltip">
1222+
Jump to HEAD
1223+
<br />
1224+
[Alt] Jump to Reference...
1225+
</span>
1226+
</GlButton>
12051227
<span>
12061228
<span className="codicon codicon-chevron-right"></span>
12071229
</span>

src/webviews/apps/plus/graph/graph.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type {
1818
UpdateStateCallback,
1919
} from '../../../../plus/webviews/graph/protocol';
2020
import {
21+
ChooseRefRequest,
2122
ChooseRepositoryCommand,
2223
DidChangeAvatarsNotification,
2324
DidChangeColumnsNotification,
@@ -106,6 +107,7 @@ export class GraphApp extends App<State> {
106107
onDoubleClickRef={(ref, metadata) => this.onDoubleClickRef(ref, metadata)}
107108
onDoubleClickRow={(row, preserveFocus) => this.onDoubleClickRow(row, preserveFocus)}
108109
onHoverRowPromise={(row: GraphRow) => this.onHoverRowPromise(row)}
110+
onJumpToRefPromise={(shift: boolean) => this.onJumpToRefPromise(shift)}
109111
onMissingAvatars={(...params) => this.onGetMissingAvatars(...params)}
110112
onMissingRefsMetadata={(...params) => this.onGetMissingRefsMetadata(...params)}
111113
onMoreRows={(...params) => this.onGetMoreRows(...params)}
@@ -557,6 +559,16 @@ export class GraphApp extends App<State> {
557559
}
558560
}
559561

562+
private async onJumpToRefPromise(alt: boolean): Promise<{ name: string; sha: string } | undefined> {
563+
try {
564+
// Assuming we have a command to get the ref details
565+
const rsp = await this.sendRequest(ChooseRefRequest, { alt: alt });
566+
return rsp;
567+
} catch {
568+
return undefined;
569+
}
570+
}
571+
560572
private onGetMissingAvatars(emails: GraphAvatars) {
561573
this.sendCommand(GetMissingAvatarsCommand, { emails: emails });
562574
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { GlButton as GlButtonWC } from './button';
2+
import { reactWrapper } from './helpers/react-wrapper';
3+
4+
export interface GlButton extends GlButtonWC {}
5+
export const GlButton = reactWrapper(GlButtonWC, { tagName: 'gl-button' });

src/webviews/apps/shared/components/button.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,8 @@ export class GlButton extends LitElement {
159159
@query('.control')
160160
protected control!: HTMLElement;
161161

162-
@property()
163-
appearance?: string;
162+
@property({ reflect: true })
163+
appearance?: 'alert' | 'secondary' | 'toolbar';
164164

165165
@property({ type: Boolean, reflect: true })
166166
disabled = false;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { CodeIcon as CodeIconWC } from './code-icon';
2+
import { reactWrapper } from './helpers/react-wrapper';
3+
4+
export interface CodeIcon extends CodeIconWC {}
5+
export const CodeIcon = reactWrapper(CodeIconWC, { tagName: 'code-icon' });

src/webviews/apps/shared/components/code-icon.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -644,19 +644,19 @@ export class CodeIcon extends LitElement {
644644
transform: rotateZ(45deg);
645645
}
646646
`;
647-
@property()
647+
@property({ reflect: true })
648648
icon = '';
649649

650-
@property()
650+
@property({ reflect: true })
651651
modifier = '';
652652

653653
@property({ type: Number })
654654
size: number | undefined = undefined;
655655

656-
@property()
656+
@property({ reflect: true })
657657
flip?: 'inline' | 'block';
658658

659-
@property()
659+
@property({ reflect: true })
660660
rotate?: '45';
661661

662662
override updated(changedProperties: Map<string, unknown>) {

0 commit comments

Comments
 (0)