Skip to content
Snippets Groups Projects
Commit 8faac89e authored by Paul Slaughter's avatar Paul Slaughter 2️⃣
Browse files

feat: Show icons for files in Merge Request changes

- Part of [this issue][1].
- Part of [this MR][2].

[1]: gitlab#386256
[2]: !152
parent 87f0d6fe
No related branches found
No related tags found
No related merge requests found
Showing
with 426 additions and 37 deletions
......@@ -5,6 +5,7 @@ import { gitlab } from './types';
import { ResponseError } from './ResponseError';
import {
createProjectBranchMutation,
getMergeRequestDiffStatsQuery,
getProjectUserPermissionsQuery,
searchProjectBranchesQuery,
} from './graphql';
......@@ -109,6 +110,14 @@ describe('GitLabClient', () => {
});
describe.each([
{
description: 'fetchMergeRequestDiffStats',
act: () => subject.fetchMergeRequestDiffStats({ mergeRequestId: '7' }),
response: mockResponses.getMergeRequestDiffStats,
expectedQuery: getMergeRequestDiffStatsQuery,
expectedVariables: { gid: 'gid://gitlab/MergeRequest/7' },
expectedReturn: mockResponses.getMergeRequestDiffStats.mergeRequest.diffStats,
},
{
description: 'fetchProjectUserPermissions',
act: () => subject.fetchProjectUserPermissions('lorem/ipsum'),
......
......@@ -6,6 +6,9 @@ import {
createProjectBranchMutation,
CreateProjectBranchResult,
CreateProjectBranchVariables,
getMergeRequestDiffStatsQuery,
GetMergeRequestDiffStatsResult,
GetMergeRequestDiffStatsVariables,
getProjectUserPermissionsResult,
getProjectUserPermissionsQuery,
getProjectUserPermissionsVariables,
......@@ -85,6 +88,17 @@ export class GitLabClient {
return result.createBranch;
}
async fetchMergeRequestDiffStats({ mergeRequestId }: { mergeRequestId: string }) {
const gid = `gid://gitlab/MergeRequest/${mergeRequestId}`;
const result = await this._graphqlClient.request<
GetMergeRequestDiffStatsResult,
GetMergeRequestDiffStatsVariables
>(getMergeRequestDiffStatsQuery, { gid });
return result.mergeRequest.diffStats;
}
fetchProject(projectId: string): Promise<gitlab.Project> {
const url = this._buildApiUrl('projects', projectId);
......
import { gql } from 'graphql-request';
export const getMergeRequestDiffStatsQuery = gql`
query getMergeRequestDiffStats($gid: MergeRequestID!) {
mergeRequest(id: $gid) {
diffStats {
path
additions
deletions
}
}
}
`;
export interface GetMergeRequestDiffStatsVariables {
gid: string;
}
export interface GetMergeRequestDiffStatsResult {
mergeRequest: {
diffStats: { path: string; additions: number; deletions: number }[];
};
}
export * from './createProjectBranch.mutation';
export * from './getMergeRequestDiffStats.query';
export * from './getProjectUserPermissions.query';
export * from './searchProjectBranches.query';
......@@ -35,6 +35,7 @@ export namespace gitlab {
target_branch: string;
target_project_id: number;
web_url: string;
diff_refs: { base_sha: string; head_sha: string; start_sha: string };
}
export interface File {
......
import type {
CreateProjectBranchResult,
GetMergeRequestDiffStatsResult,
getProjectUserPermissionsResult,
SearchProjectBranchesResult,
} from '../../src/graphql';
......@@ -13,6 +14,18 @@ export const createProjectBranch: CreateProjectBranchResult = {
},
};
export const getMergeRequestDiffStats: GetMergeRequestDiffStatsResult = {
mergeRequest: {
diffStats: [
{
path: 'README.md',
additions: 7,
deletions: 77,
},
],
},
};
export const getProjectUserPermissions: getProjectUserPermissionsResult = {
project: {
userPermissions: {
......
......@@ -13,6 +13,7 @@ import {
COMMAND_TRACK_EVENT,
COMMAND_FETCH_PROJECT_BRANCHES,
COMMAND_CREATE_PROJECT_BRANCH,
COMMAND_FETCH_MERGE_REQUEST_DIFF_STATS,
} from './index';
import * as commit from './commit';
import * as start from './start';
......@@ -93,6 +94,7 @@ describe('vscode-mediator-commands/commands', () => {
});
describe.each<[string, keyof GitLabClient, unknown]>([
[COMMAND_FETCH_MERGE_REQUEST_DIFF_STATS, 'fetchMergeRequestDiffStats', { mergeRequestId: '7' }],
[
COMMAND_FETCH_PROJECT_BRANCHES,
'fetchProjectBranches',
......
......@@ -16,6 +16,7 @@ const PREFIX = 'gitlab-web-ide.mediator';
// region: command names -----------------------------------------------
export const COMMAND_START = `${PREFIX}.start`;
export const COMMAND_FETCH_FILE_RAW = `${PREFIX}.fetch-file-raw`;
export const COMMAND_FETCH_MERGE_REQUEST_DIFF_STATS = `${PREFIX}.fetch-merge-request-diff-stats`;
export const COMMAND_FETCH_PROJECT_BRANCHES = `${PREFIX}.fetch-project-branches`;
export const COMMAND_CREATE_PROJECT_BRANCH = `${PREFIX}.create-project-branch`;
export const COMMAND_READY = `${PREFIX}.ready`;
......@@ -31,6 +32,12 @@ export const COMMAND_TRACK_EVENT = `${PREFIX}.track-event`;
// web-ide-vscode-extension which actually calls these commands
export type StartRemoteMessageParams = StartRemoteMessage['params'];
export type PreventUnloadMessageParams = PreventUnloadMessage['params'];
export type FetchMergeRequestDiffStatsParams = Parameters<
GitLabClient['fetchMergeRequestDiffStats']
>[0];
export type FetchMergeRequestDiffStatsResponse = Awaited<
ReturnType<GitLabClient['fetchMergeRequestDiffStats']>
>;
export type FetchProjectBranchesParams = Parameters<GitLabClient['fetchProjectBranches']>[0];
export type FetchProjectBranchesResponse = Awaited<
ReturnType<GitLabClient['fetchProjectBranches']>
......@@ -59,6 +66,11 @@ export const createCommands = (config: IFullConfig, bufferWrapper: VSBufferWrapp
id: COMMAND_FETCH_PROJECT_BRANCHES,
handler: (params: FetchProjectBranchesParams) => client.fetchProjectBranches(params),
},
{
id: COMMAND_FETCH_MERGE_REQUEST_DIFF_STATS,
handler: (params: FetchMergeRequestDiffStatsParams) =>
client.fetchMergeRequestDiffStats(params),
},
{
id: COMMAND_CREATE_PROJECT_BRANCH,
handler: (params: CreateProjectBranchParams) => client.createProjectBranch(params),
......
......@@ -31,6 +31,11 @@ const TEST_MR: gitlab.MergeRequest = {
target_branch: 'mr-target-branch',
target_project_id: 17,
web_url: 'https://gitlab.example.com/mr/1',
diff_refs: {
base_sha: '000001',
head_sha: '000002',
start_sha: '000003',
},
};
const TEST_PROJECT = createTestProject(TEST_CONFIG.projectPath);
const TEST_EMPTY_PROJECT = { ...TEST_PROJECT, empty_repo: true };
......@@ -75,8 +80,6 @@ describe('vscode-mediator-commands/commands/start', () => {
project: TEST_PROJECT,
repoRoot: TEST_CONFIG.repoRoot,
files: TEST_BLOBS,
isMergeRequestBranch: false,
mergeRequestUrl: '',
gitlabUrl: TEST_CONFIG.gitlabUrl,
userPermissions: TEST_USER_PERMISSIONS,
});
......@@ -118,8 +121,12 @@ describe('vscode-mediator-commands/commands/start', () => {
branch: {
name: branchName,
},
isMergeRequestBranch,
mergeRequestUrl: TEST_MR.web_url,
mergeRequest: {
id: String(TEST_MR.id),
baseSha: TEST_MR.diff_refs.base_sha,
mergeRequestUrl: TEST_MR.web_url,
isMergeRequestBranch,
},
});
});
},
......
......@@ -41,6 +41,25 @@ const createEmptyBranch = (name: string): gitlab.Branch => ({
name,
});
const getMergeRequestContext = ({
mergeRequest,
branchName,
}: {
mergeRequest?: gitlab.MergeRequest;
branchName: string;
}): StartCommandResponse['mergeRequest'] => {
if (!mergeRequest) {
return undefined;
}
return {
id: String(mergeRequest.id),
baseSha: mergeRequest.diff_refs.base_sha,
isMergeRequestBranch: branchName === mergeRequest.source_branch,
mergeRequestUrl: mergeRequest.web_url,
};
};
export const commandFactory =
(config: IFullConfig, client: GitLabClient): StartCommand =>
async (options: StartCommandOptions = {}): Promise<StartCommandResponse> => {
......@@ -71,18 +90,14 @@ export const commandFactory =
const blobs = tree.filter(item => item.type === 'blob');
const mergeRequestUrl = mergeRequest?.web_url || '';
const isMergeRequestBranch = Boolean(mergeRequest && branchName === mergeRequest.source_branch);
return {
gitlabUrl: config.gitlabUrl,
branch,
files: blobs,
repoRoot: config.repoRoot,
project,
isMergeRequestBranch,
mergeRequestUrl,
userPermissions,
mergeRequest: getMergeRequestContext({ mergeRequest, branchName }),
forkInfo: config.forkInfo,
};
};
......@@ -10,6 +10,20 @@ export type GitLabProject = gitlab.Project;
// region: Mediator specific types -------------------------------------
interface MergeRequestContext {
// id - Global ID (not iid) of the MergeRequest
id: string;
// isMergeRequestBranch - true if the branch has a corresponding MR URL
isMergeRequestBranch: boolean;
// mergeRequestUrl - the existing MR URL of the branch
mergeRequestUrl: string;
// baseSha - the base SHA of the MergeRequest (used for viewing MR changes)
baseSha: string;
}
export interface IFullConfig extends ClientOnlyConfig {
repoRoot: string;
}
......@@ -34,17 +48,14 @@ export interface StartCommandResponse {
// repoRoot - the root path of the FileSystem where the main repository lives
repoRoot: string;
// isMergeRequestBranch - true if the branch has a corresponding MR URL
isMergeRequestBranch?: boolean;
// userPermissions - current user permissions for the project
userPermissions: ProjectUserPermissions;
// mergeRequestUrl - the existing MR URL of the branch
mergeRequestUrl?: string;
// mergeRequest - contains optional MergeRequest properties of the current Web IDE context
mergeRequest?: MergeRequestContext;
// forkInfo - fork info about the project from the Config
forkInfo?: ForkInfo;
// userPermissions - current user permissions for the project
userPermissions: ProjectUserPermissions;
}
export interface ICommand {
......
......@@ -102,6 +102,9 @@ module.exports = {
ThemeColor: function ThemeColor(name) {
this.name = name;
},
ThemeIcon: function ThemeIcon(name) {
this.name = name;
},
StatusBarAlignment: {
Left: 0,
Right: 1,
......
import * as vscode from 'vscode';
import { RELOAD_COMMAND_ID } from '../constants';
import { registerReloadCommand } from './reload';
import { createFakeCancellationToken, createFakeProgress } from '../../test-utils/vscode';
const TEST_REF = 'new-branch-patch-123';
......@@ -73,19 +74,24 @@ describe('commands/reload', () => {
{
cancellable: false,
location: vscode.ProgressLocation.Notification,
title: 'Reloading file system...',
title: 'Reloading',
},
expect.any(Function),
);
});
it('calls reloadFn with progress', () => {
it('calls reloadFn with progress', async () => {
expect(reloadFn).not.toHaveBeenCalled();
const [, fn] = (vscode.window.withProgress as jest.Mock).mock.calls[0];
fn();
const progress = createFakeProgress();
const cancellationToken = createFakeCancellationToken();
expect(reloadFn).toHaveBeenCalledWith(disposables, { ref: TEST_REF });
const [, fn] = jest.mocked(vscode.window.withProgress).mock.calls[0];
await fn(progress, cancellationToken);
expect(reloadFn).toHaveBeenCalledWith(disposables, progress, {
ref: TEST_REF,
});
});
});
});
......
......@@ -4,6 +4,7 @@ import { RELOAD_COMMAND_ID } from '../constants';
type ReloadCallback = (
disposables: vscode.Disposable[],
progress: vscode.Progress<{ increment: number; message: string }>,
options: StartCommandOptions,
) => Promise<unknown>;
......@@ -42,9 +43,9 @@ export const registerReloadCommand = (
{
cancellable: false,
location: vscode.ProgressLocation.Notification,
title: 'Reloading file system...',
title: 'Reloading',
},
() => reloadFn(disposables, { ref }),
progress => reloadFn(disposables, progress, { ref }),
);
},
);
......
// TODO: For some reason `ts-jest` isn't finding the `.d.ts` files
import '../vscode.proposed.codiconDecoration.d';
import * as vscode from 'vscode';
import { basename } from '@gitlab/utils-path';
import { initMergeRequestContext, MSG_LOADING_MERGE_REQUEST } from './initMergeRequestContext';
import { fetchMergeRequestDiffStats } from './mediator';
import { createFakeProgress } from '../test-utils/vscode';
import { MergeRequestFileDecorationProvider } from './vscode/MergeRequestFileDecorationProvider';
jest.mock('./mediator');
const createTestFile = (type: 'blob' | 'tree', path: string) => ({
id: path,
type,
path,
mode: '',
name: basename(path),
});
const TEST_OPTIONS: Parameters<typeof initMergeRequestContext>[2] = {
repoRoot: 'gitlab-test',
files: [
// Ensure that we are cleaning leading separator
createTestFile('blob', '/README.md'),
createTestFile('tree', 'src'),
createTestFile('blob', 'src/untouched.js'),
createTestFile('tree', 'src/bar'),
createTestFile('blob', 'src/bar/test.rs'),
],
mergeRequest: {
id: '7',
baseSha: '000111',
isMergeRequestBranch: true,
mergeRequestUrl: 'https://gitlab.com/gitlab-org/gitlab-web-ide/-/merge_requests/1',
},
};
const TEST_DIFF_STATS: Awaited<ReturnType<typeof fetchMergeRequestDiffStats>> = [
{
path: 'README.md',
additions: 7,
deletions: 7,
},
{
path: 'src/deleteme.js',
additions: 0,
deletions: 10,
},
{
// Ensure that we are cleaning leading separator
path: '/src/bar/test.rs',
additions: 10,
deletions: 0,
},
];
describe('initMergeRequestContext', () => {
let progress: vscode.Progress<{ increment: number; message: string }>;
let disposables: vscode.Disposable[];
beforeEach(() => {
disposables = [];
progress = createFakeProgress();
jest
.mocked(vscode.window.registerFileDecorationProvider)
.mockImplementation(() => ({ dispose: jest.fn() }));
});
describe('default', () => {
beforeEach(async () => {
jest.mocked(fetchMergeRequestDiffStats).mockResolvedValue(TEST_DIFF_STATS);
await initMergeRequestContext(disposables, progress, TEST_OPTIONS);
});
it('calls progress', () => {
expect(progress.report).toHaveBeenCalledWith({
increment: -1,
message: MSG_LOADING_MERGE_REQUEST,
});
});
it('fetches merge request diff stats', () => {
expect(fetchMergeRequestDiffStats).toHaveBeenCalledWith({
mergeRequestId: TEST_OPTIONS.mergeRequest.id,
});
});
it('registers merge request file decoration provider', () => {
const provider = jest.mocked(vscode.window.registerFileDecorationProvider).mock.calls[0][0];
const disposable = jest.mocked(vscode.window.registerFileDecorationProvider).mock.results[0]
.value;
expect(provider).toEqual(
new MergeRequestFileDecorationProvider(
new Set([
// Note the inclusion of parents and the lack of `deleteme.js`
'/gitlab-test',
'/gitlab-test/README.md',
'/gitlab-test/src/bar/test.rs',
'/gitlab-test/src/bar',
'/gitlab-test/src',
]),
),
);
expect(disposables).toContain(disposable);
});
});
it.todo('when fetch diff stats fails');
});
import { cleanLeadingSeparator, joinPaths, splitParent } from '@gitlab/utils-path';
import { StartCommandResponse } from '@gitlab/vscode-mediator-commands';
import * as vscode from 'vscode';
import { fetchMergeRequestDiffStats } from './mediator';
import { MergeRequestFileDecorationProvider } from './vscode/MergeRequestFileDecorationProvider';
// why: Export for testing
export const MSG_LOADING_MERGE_REQUEST = 'Loading merge request context...';
type InitMergeRequestOptions = {
mergeRequest: NonNullable<StartCommandResponse['mergeRequest']>;
files: StartCommandResponse['files'];
repoRoot: string;
};
const getMergeRequestChanges = async ({
mergeRequest,
files,
repoRoot,
}: InitMergeRequestOptions) => {
const allFiles = new Set(
files.filter(x => x.type === 'blob').map(x => cleanLeadingSeparator(x.path)),
);
const diffStats = await fetchMergeRequestDiffStats({
mergeRequestId: mergeRequest.id,
});
return (
diffStats
// what: We are only interesting in Modified/Created changes. Deletions
// aren't presentable at the moment, so let's ignore them.
.filter(x => allFiles.has(cleanLeadingSeparator(x.path)))
.map(x => ({
...x,
path: joinPaths('/', repoRoot, x.path),
}))
);
};
const addAllParentPathsToSet = (pathsSet: Set<string>, path: string) => {
if (!path || pathsSet.has(path)) {
return;
}
pathsSet.add(path);
const [parent] = splitParent(path);
if (parent) {
addAllParentPathsToSet(pathsSet, parent);
}
};
const createPathsSet = (diffs: { path: string }[]): ReadonlySet<string> => {
const pathsSet = new Set<string>();
diffs.forEach(({ path }) => {
addAllParentPathsToSet(pathsSet, path);
});
return pathsSet;
};
export const initMergeRequestContext = async (
disposables: vscode.Disposable[],
progress: vscode.Progress<{ increment: number; message: string }>,
options: InitMergeRequestOptions,
) => {
progress.report({ increment: -1, message: MSG_LOADING_MERGE_REQUEST });
const mrChanges = await getMergeRequestChanges(options);
const mrPathsSet = createPathsSet(mrChanges);
disposables.push(
vscode.window.registerFileDecorationProvider(
new MergeRequestFileDecorationProvider(mrPathsSet),
),
);
};
......@@ -12,6 +12,9 @@ import { FS_SCHEME, GET_STARTED_WALKTHROUGH_ID, WEB_IDE_READY_CONTEXT_ID } from
import { registerReloadCommand } from './commands/reload';
import { initBranchStatusBarItem } from './ui';
import { showCannotPushCodeWarning } from './ui/showCannotPushCodeWarning';
import { initMergeRequestContext } from './initMergeRequestContext';
const MSG_LOADING_GITLAB_WEB_IDE = 'Loading GitLab Web IDE...';
function initializeFileSystemProvider(
disposables: vscode.Disposable[],
......@@ -43,22 +46,17 @@ function refreshFileView() {
*/
async function initialize(
disposables: vscode.Disposable[],
progress: vscode.Progress<{ increment: number; message: string }>,
startOptions: StartCommandOptions = {},
) {
progress.report({ increment: -1, message: MSG_LOADING_GITLAB_WEB_IDE });
const startResponse = start(startOptions);
registerCommands(disposables, startResponse);
const {
files,
branch,
repoRoot,
project,
isMergeRequestBranch,
mergeRequestUrl = '',
userPermissions,
forkInfo,
} = await startResponse;
const { files, branch, repoRoot, project, mergeRequest, userPermissions, forkInfo } =
await startResponse;
// If user can't push, show warning message
if (!userPermissions.pushCode) {
......@@ -68,7 +66,9 @@ async function initialize(
}
// If we are on the merge request branch, consider the merge request URL assoc with the branch
const branchMergeRequestUrl = isMergeRequestBranch ? mergeRequestUrl : '';
const branchMergeRequestUrl = mergeRequest?.isMergeRequestBranch
? mergeRequest.mergeRequestUrl
: '';
const { fs, sourceControl, sourceControlFs } = await createSystems({
contentProvider: new GitLabFileContentProvider(branch.commit.id),
......@@ -102,9 +102,18 @@ async function initialize(
await refreshFileView();
// what: Declare to the parent context that the Web IDE is "ready"
await vscode.commands.executeCommand('setContext', WEB_IDE_READY_CONTEXT_ID, true);
await ready();
// what: We can load this extra context after we are "ready"
if (mergeRequest?.isMergeRequestBranch) {
await initMergeRequestContext(disposables, progress, {
mergeRequest,
files,
repoRoot,
});
}
}
/**
......@@ -115,9 +124,9 @@ function initializeWithProgress(disposables: vscode.Disposable[]) {
{
cancellable: false,
location: vscode.ProgressLocation.Notification,
title: 'Initializing GitLab Web IDE...',
title: 'Initializing',
},
() => initialize(disposables),
progress => initialize(disposables, progress),
);
}
......
......@@ -7,6 +7,7 @@ import {
COMMAND_COMMIT,
COMMAND_PREVENT_UNLOAD,
COMMAND_SET_HREF,
COMMAND_FETCH_MERGE_REQUEST_DIFF_STATS,
COMMAND_FETCH_PROJECT_BRANCHES,
COMMAND_CREATE_PROJECT_BRANCH,
} from '@gitlab/vscode-mediator-commands';
......@@ -15,6 +16,8 @@ import type {
VSCodeBuffer,
StartRemoteMessageParams,
PreventUnloadMessageParams,
FetchMergeRequestDiffStatsParams,
FetchMergeRequestDiffStatsResponse,
FetchProjectBranchesParams,
FetchProjectBranchesResponse,
CreateProjectBranchParams,
......@@ -29,6 +32,11 @@ export const start = (options: StartCommandOptions = {}): Thenable<StartCommandR
export const fetchFileRaw = (ref: string, path: string): Thenable<VSCodeBuffer> =>
vscode.commands.executeCommand(COMMAND_FETCH_FILE_RAW, ref, path);
export const fetchMergeRequestDiffStats = (
params: FetchMergeRequestDiffStatsParams,
): Thenable<FetchMergeRequestDiffStatsResponse> =>
vscode.commands.executeCommand(COMMAND_FETCH_MERGE_REQUEST_DIFF_STATS, params);
export const fetchProjectBranches = (
params: FetchProjectBranchesParams,
): Thenable<FetchProjectBranchesResponse> =>
......
// TODO: For some reason `ts-jest` isn't finding the `.d.ts` files
import '../../vscode.proposed.codiconDecoration.d';
import * as vscode from 'vscode';
import {
FILE_DECORATION,
MergeRequestFileDecorationProvider,
} from './MergeRequestFileDecorationProvider';
import { FS_SCHEME } from '../constants';
const TEST_MR_CHANGES = ['/gitlab-ui/README.md', '/gitlab-ui/src', '/gitlab-ui/src/foo.js'];
describe('vscode/MergeRequestFileDecorationProvider', () => {
let subject: MergeRequestFileDecorationProvider;
beforeEach(() => {
subject = new MergeRequestFileDecorationProvider(new Set(TEST_MR_CHANGES));
});
it.each`
uri | decoration
${`${FS_SCHEME}:///gitlab-ui/README.md`} | ${FILE_DECORATION}
${`some-other-scheme:///gitlab-ui/README.md`} | ${undefined}
${`${FS_SCHEME}:///gitlab-ui/src`} | ${FILE_DECORATION}
${`${FS_SCHEME}:///gitlab-ui/doc`} | ${undefined}
`('with "$uri", provides decoration "$decoration"', async ({ uri, decoration }) => {
const result = await subject.provideFileDecoration(vscode.Uri.parse(uri));
expect(result).toEqual(decoration);
});
});
import * as vscode from 'vscode';
import { FS_SCHEME } from '../constants';
// why: Export for testing purposes
export const FILE_DECORATION: vscode.FileDecoration2 = {
badge: new vscode.ThemeIcon('git-pull-request'),
propagate: false,
tooltip: 'Part of merge request changes',
};
export class MergeRequestFileDecorationProvider implements vscode.FileDecorationProvider {
private readonly _mrChanges: ReadonlySet<string>;
constructor(mrChanges: ReadonlySet<string>) {
this._mrChanges = mrChanges;
}
provideFileDecoration(uri: vscode.Uri): vscode.ProviderResult<vscode.FileDecoration> {
if (uri.scheme === FS_SCHEME && this._mrChanges.has(uri.path)) {
// why: Let's clone for defensiveness
const decoration = { ...FILE_DECORATION };
// why: This `as FileDecoration` is the way this proposed API is handled in other extensions
// https://sourcegraph.com/github.com/microsoft/vscode-pull-request-github@93129e3085e1a45aaf5a4f30f11a2333cca85e3b/-/blob/src/view/prStatusDecorationProvider.ts?L51:5
return decoration as vscode.FileDecoration;
}
return undefined;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment