提交 750bae4b 编写于 作者: T Tomas Vik

feat: create snippet patch

上级 16c65cca
......@@ -83,6 +83,10 @@
"command": "gl.openProjectPage",
"title": "GitLab: Open current project on GitLab"
},
{
"command": "gl.createSnippetPatch",
"title": "GitLab: Create snippet patch"
},
{
"command": "gl.openCurrentPipeline",
"title": "GitLab: Open current pipeline on GitLab"
......@@ -289,6 +293,10 @@
{
"command": "gl.cloneWiki",
"when": "!gitlab:noToken"
},
{
"command": "gl.createSnippetPatch",
"when": "gitlab:validState"
}
],
"view/title": [
......
......@@ -23,8 +23,9 @@ module.exports = {
showWarningMessage: jest.fn(),
showErrorMessage: jest.fn(),
createStatusBarItem: jest.fn(),
withProgress: jest.fn(),
showInputBox: jest.fn(),
showQuickPick: jest.fn(),
withProgress: jest.fn(),
createQuickPick: jest.fn(),
},
commands: {
......
......@@ -33,6 +33,7 @@ export const USER_COMMANDS = {
CREATE_COMMENT: 'gl.createComment',
CHECKOUT_MR_BRANCH: 'gl.checkoutMrBranch',
CLONE_WIKI: 'gl.cloneWiki',
CREATE_SNIPPET_PATCH: 'gl.createSnippetPatch',
};
/*
......
......@@ -4,20 +4,21 @@ import * as gitLabService from '../gitlab_service';
import { gitExtensionWrapper } from '../git/git_extension_wrapper';
import { GitLabProject } from '../gitlab/gitlab_project';
const visibilityOptions = [
{
label: 'Public',
type: 'public',
},
{
label: 'Internal',
type: 'internal',
},
{
label: 'Private',
type: 'private',
},
];
type VisibilityItem = vscode.QuickPickItem & { type: string };
const PRIVATE_VISIBILITY_ITEM: VisibilityItem = {
label: '$(lock) Private',
type: 'private',
description: 'The snippet is visible only to project members.',
};
const PUBLIC_VISIBILITY_ITEM: VisibilityItem = {
label: '$(globe) Public',
type: 'public',
description: 'The snippet can be accessed without any authentication.',
};
export const VISIBILITY_OPTIONS = [PRIVATE_VISIBILITY_ITEM, PUBLIC_VISIBILITY_ITEM];
const contextOptions = [
{
......@@ -82,7 +83,7 @@ export async function createSnippet() {
return;
}
const visibility = await vscode.window.showQuickPick(visibilityOptions);
const visibility = await vscode.window.showQuickPick(VISIBILITY_OPTIONS);
if (!visibility) return;
const context = await vscode.window.showQuickPick(contextOptions);
......
import * as vscode from 'vscode';
import { createSnippetPatch } from './create_snippet_patch';
import { WrappedRepository } from '../git/wrapped_repository';
import { project } from '../test_utils/entities';
import { gitExtensionWrapper } from '../git/git_extension_wrapper';
import { asMock } from '../test_utils/as_mock';
import { createSnippet } from '../gitlab_service';
import { openUrl } from '../openers';
jest.mock('../git/git_extension_wrapper');
jest.mock('../gitlab_service');
jest.mock('../openers');
const SNIPPET_URL = 'https://gitlab.com/test-group/test-project/-/snippets/2146265';
const DIFF_OUTPUT = 'diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml';
describe('create snippet patch', () => {
let wrappedRepository: WrappedRepository;
beforeEach(() => {
const mockRepository: Partial<WrappedRepository> = {
lastCommitSha: 'abcd1234567',
getTrackingBranchName: async () => 'tracking-branch-name',
getProject: async () => project,
diff: async () => DIFF_OUTPUT,
};
wrappedRepository = mockRepository as WrappedRepository;
asMock(gitExtensionWrapper.getActiveRepositoryOrSelectOne).mockResolvedValue(wrappedRepository);
asMock(vscode.window.showInputBox).mockResolvedValue('snippet_name');
asMock(vscode.window.showQuickPick).mockImplementation(options =>
options.filter((o: any) => o.type === 'private').pop(),
);
asMock(createSnippet).mockResolvedValue({
web_url: SNIPPET_URL,
});
});
afterEach(() => {
jest.resetAllMocks();
});
it('creates a snippet patch and opens it in a browser', async () => {
await createSnippetPatch();
expect(openUrl).toHaveBeenCalledWith(SNIPPET_URL);
});
describe('populating the create snippet request', () => {
let formData: Record<string, string>;
beforeEach(async () => {
await createSnippetPatch();
[[, formData]] = asMock(createSnippet).mock.calls;
});
it('prepends "patch: " to the user input to create snippet title', () => {
expect(formData.title).toBe('patch: snippet_name');
});
it('appends ".patch" to the user input to create snippet file name', () => {
expect(formData.file_name).toBe('snippet_name.patch');
});
it("sets user's choice of visibility (private selected in test setup)", () => {
expect(formData.visibility).toBe('private');
});
it('sets the diff command output as the blob content', () => {
expect(formData.content).toBe(DIFF_OUTPUT);
});
});
});
import * as vscode from 'vscode';
import * as assert from 'assert';
import * as gitLabService from '../gitlab_service';
import * as openers from '../openers';
import { gitExtensionWrapper } from '../git/git_extension_wrapper';
import { VISIBILITY_OPTIONS } from './create_snippet';
const getSnippetPatchDescription = (
branch: string,
commit: string,
patchFileName: string,
): string => `
This snippet contains suggested changes for branch ${branch} (commit: ${commit}).
Apply this snippet:
- In VS Code with the GitLab Workflow extension installed:
- Run \`GitLab: Apply snippet patch\` and select this snippet
- Using the \`git\` command:
- Download the \`${patchFileName}\` file to your project folder
- In your project folder, run
~~~sh
git apply '${patchFileName}'
~~~
*This snippet was created with the [GitLab Workflow VS Code extension](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow).*
`;
export const createSnippetPatch = async (): Promise<void> => {
const repository = await gitExtensionWrapper.getActiveRepositoryOrSelectOne();
assert(repository);
assert(repository.lastCommitSha);
const patch = await repository.diff();
const name = await vscode.window.showInputBox({
placeHolder: 'patch name',
prompt:
'The name is used as the snippet title and also as the filename (with .patch appended).',
});
if (!name) return;
const visibility = await vscode.window.showQuickPick(VISIBILITY_OPTIONS);
if (!visibility) return;
const project = await repository.getProject();
assert(project);
const patchFileName = `${name}.patch`;
const data = {
id: project.restId,
title: `patch: ${name}`,
description: getSnippetPatchDescription(
await repository.getTrackingBranchName(),
repository.lastCommitSha,
patchFileName,
),
file_name: patchFileName,
visibility: visibility.type,
content: patch,
};
const snippet = await gitLabService.createSnippet(repository.rootFsPath, data);
await openers.openUrl(snippet.web_url);
};
......@@ -32,6 +32,7 @@ const { changeTypeDecorationProvider } = require('./review/change_type_decoratio
const { checkVersion } = require('./utils/check_version');
const { checkoutMrBranch } = require('./commands/checkout_mr_branch');
const { cloneWiki } = require('./commands/clone_wiki');
const { createSnippetPatch } = require('./commands/create_snippet_patch');
vscode.gitLabWorkflow = {
sidebarDataProviders: [],
......@@ -92,6 +93,7 @@ const registerCommands = (context, outputChannel) => {
[USER_COMMANDS.CREATE_COMMENT]: createComment,
[USER_COMMANDS.CHECKOUT_MR_BRANCH]: checkoutMrBranch,
[USER_COMMANDS.CLONE_WIKI]: cloneWiki,
[USER_COMMANDS.CREATE_SNIPPET_PATCH]: createSnippetPatch,
[PROGRAMMATIC_COMMANDS.NO_IMAGE_REVIEW]: () =>
vscode.window.showInformationMessage("GitLab MR review doesn't support images yet."),
};
......
......@@ -167,6 +167,10 @@ export class WrappedRepository {
return this.rawRepository.show(sha, absolutePath).catch(() => null);
}
async diff(): Promise<string> {
return this.rawRepository.diff();
}
async getTrackingBranchName(): Promise<string> {
const branchName = this.rawRepository.state.HEAD?.name;
assert(
......
......@@ -3,12 +3,11 @@ import * as gitLabService from './gitlab_service';
import { pipeline, mr, issue } from './test_utils/entities';
import { USER_COMMANDS } from './command_names';
import { gitExtensionWrapper } from './git/git_extension_wrapper';
import { asMock } from './test_utils/as_mock';
jest.mock('./gitlab_service');
jest.mock('./git/git_extension_wrapper');
const asMock = (mockFn: unknown) => mockFn as jest.Mock;
asMock(vscode.workspace.getConfiguration).mockReturnValue({
showStatusBarLinks: true,
showIssueLinkOnStatusBar: true,
......
export const asMock = (mockFn: unknown) => mockFn as jest.Mock;
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册