Родитель
9cd82696c4
Коммит
a963d76820
|
@ -89,6 +89,7 @@ export interface CreatePullRequestNew {
|
|||
// #region new create view
|
||||
|
||||
export interface CreateParamsNew {
|
||||
canModifyBranches: boolean;
|
||||
defaultBaseRemote?: RemoteInfo;
|
||||
defaultBaseBranch?: string;
|
||||
defaultCompareRemote?: RemoteInfo;
|
||||
|
|
|
@ -685,7 +685,7 @@
|
|||
"id": "github:createPullRequestWebview",
|
||||
"type": "webview",
|
||||
"name": "%view.github.create.pull.request.name%",
|
||||
"when": "github:createPullRequest",
|
||||
"when": "github:createPullRequest || github:revertPullRequest",
|
||||
"visibility": "visible",
|
||||
"initialSize": 2
|
||||
},
|
||||
|
@ -706,7 +706,7 @@
|
|||
{
|
||||
"id": "prStatus:github",
|
||||
"name": "%view.pr.status.github.name%",
|
||||
"when": "github:inReviewMode && !github:createPullRequest",
|
||||
"when": "github:inReviewMode && !github:createPullRequest && !github:revertPullRequest",
|
||||
"icon": "$(git-pull-request)",
|
||||
"visibility": "visible",
|
||||
"initialSize": 3
|
||||
|
@ -715,7 +715,7 @@
|
|||
"id": "github:activePullRequest",
|
||||
"type": "webview",
|
||||
"name": "%view.github.active.pull.request.name%",
|
||||
"when": "github:inReviewMode && github:focusedReview && !github:createPullRequest && github:activePRCount <= 1",
|
||||
"when": "github:inReviewMode && github:focusedReview && !github:createPullRequest && !github:revertPullRequest && github:activePRCount <= 1",
|
||||
"initialSize": 2
|
||||
},
|
||||
{
|
||||
|
|
|
@ -28,5 +28,13 @@ declare module 'vscode' {
|
|||
* Worth noting that we already have this problem for the `comments` property.
|
||||
*/
|
||||
state?: CommentThreadState | { resolved?: CommentThreadState; applicability?: CommentThreadApplicability };
|
||||
readonly uri: Uri;
|
||||
range: Range | undefined;
|
||||
comments: readonly Comment[];
|
||||
collapsibleState: CommentThreadCollapsibleState;
|
||||
canReply: boolean;
|
||||
contextValue?: string;
|
||||
label?: string;
|
||||
dispose(): void;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,68 +6,27 @@
|
|||
declare module 'vscode' {
|
||||
|
||||
export interface CommentThread2 {
|
||||
/**
|
||||
* The uri of the document the thread has been created on.
|
||||
*/
|
||||
readonly uri: Uri;
|
||||
|
||||
/**
|
||||
* The range the comment thread is located within the document. The thread icon will be shown
|
||||
* at the last line of the range.
|
||||
* at the last line of the range. When set to undefined, the comment will be associated with the
|
||||
* file, and not a specific range.
|
||||
*/
|
||||
range: Range | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The ranges a CommentingRangeProvider enables commenting on.
|
||||
*/
|
||||
export interface CommentingRanges {
|
||||
/**
|
||||
* Enables comments to be added to a file without a specific range.
|
||||
*/
|
||||
enableFileComments: boolean;
|
||||
|
||||
/**
|
||||
* The ordered comments of the thread.
|
||||
* The ranges which allow new comment threads creation.
|
||||
*/
|
||||
comments: readonly Comment[];
|
||||
|
||||
/**
|
||||
* Whether the thread should be collapsed or expanded when opening the document.
|
||||
* Defaults to Collapsed.
|
||||
*/
|
||||
collapsibleState: CommentThreadCollapsibleState;
|
||||
|
||||
/**
|
||||
* Whether the thread supports reply.
|
||||
* Defaults to true.
|
||||
*/
|
||||
canReply: boolean;
|
||||
|
||||
/**
|
||||
* Context value of the comment thread. This can be used to contribute thread specific actions.
|
||||
* For example, a comment thread is given a context value as `editable`. When contributing actions to `comments/commentThread/title`
|
||||
* using `menus` extension point, you can specify context value for key `commentThread` in `when` expression like `commentThread == editable`.
|
||||
* ```json
|
||||
* "contributes": {
|
||||
* "menus": {
|
||||
* "comments/commentThread/title": [
|
||||
* {
|
||||
* "command": "extension.deleteCommentThread",
|
||||
* "when": "commentThread == editable"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* This will show action `extension.deleteCommentThread` only for comment threads with `contextValue` is `editable`.
|
||||
*/
|
||||
contextValue?: string;
|
||||
|
||||
/**
|
||||
* The optional human-readable label describing the {@link CommentThread Comment Thread}
|
||||
*/
|
||||
label?: string;
|
||||
|
||||
// from the commentThreadRelevance proposal
|
||||
state?: CommentThreadState | { resolved?: CommentThreadState; applicability?: CommentThreadApplicability };
|
||||
|
||||
/**
|
||||
* Dispose this comment thread.
|
||||
*
|
||||
* Once disposed, this comment thread will be removed from visible editors and Comment Panel when appropriate.
|
||||
*/
|
||||
dispose(): void;
|
||||
ranges?: Range[];
|
||||
}
|
||||
|
||||
export interface CommentController {
|
||||
|
@ -78,6 +37,6 @@ declare module 'vscode' {
|
|||
/**
|
||||
* Provide a list of ranges which allow new comment threads creation or null for a given document
|
||||
*/
|
||||
provideCommentingRanges(document: TextDocument, token: CancellationToken): ProviderResult<Range[] | { enableFileComments: boolean; ranges?: Range[] }>;
|
||||
provideCommentingRanges(document: TextDocument, token: CancellationToken): ProviderResult<Range[] | CommentingRanges>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,6 @@ function ensurePR(folderRepoManager: FolderRepositoryManager, pr?: PRNode | Pull
|
|||
}
|
||||
|
||||
export async function openDescription(
|
||||
context: vscode.ExtensionContext,
|
||||
telemetry: ITelemetry,
|
||||
pullRequestModel: PullRequestModel,
|
||||
descriptionNode: DescriptionNode | undefined,
|
||||
|
@ -74,7 +73,7 @@ export async function openDescription(
|
|||
descriptionNode?.reveal(descriptionNode, { select: true, focus: true });
|
||||
}
|
||||
// Create and show a new webview
|
||||
await PullRequestOverviewPanel.createOrShow(context.extensionUri, folderManager, pullRequest, undefined, preserveFocus);
|
||||
await PullRequestOverviewPanel.createOrShow(telemetry, folderManager.context.extensionUri, folderManager, pullRequest, undefined, preserveFocus);
|
||||
|
||||
if (notificationProvider?.hasNotification(pullRequest)) {
|
||||
notificationProvider.markPrNotificationsAsRead(pullRequest);
|
||||
|
@ -124,7 +123,7 @@ export function registerCommands(
|
|||
reposManager: RepositoriesManager,
|
||||
reviewsManager: ReviewsManager,
|
||||
telemetry: ITelemetry,
|
||||
tree: PullRequestsTreeDataProvider
|
||||
tree: PullRequestsTreeDataProvider,
|
||||
) {
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand(
|
||||
|
@ -812,7 +811,7 @@ export function registerCommands(
|
|||
descriptionNode = reviewManager.changesInPrDataProvider.getDescriptionNode(folderManager);
|
||||
}
|
||||
|
||||
await openDescription(context, telemetry, pullRequestModel, descriptionNode, folderManager, !(argument instanceof DescriptionNode), !(argument instanceof RepositoryChangesNode), tree.notificationProvider);
|
||||
await openDescription(telemetry, pullRequestModel, descriptionNode, folderManager, !(argument instanceof DescriptionNode), !(argument instanceof RepositoryChangesNode), tree.notificationProvider);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -843,7 +842,7 @@ export function registerCommands(
|
|||
const pullRequest = ensurePR(folderManager, pr);
|
||||
descriptionNode.reveal(descriptionNode, { select: true, focus: true });
|
||||
// Create and show a new webview
|
||||
PullRequestOverviewPanel.createOrShow(context.extensionUri, folderManager, pullRequest, true);
|
||||
PullRequestOverviewPanel.createOrShow(telemetry, context.extensionUri, folderManager, pullRequest, true);
|
||||
|
||||
/* __GDPR__
|
||||
"pr.openDescriptionToTheSide" : {}
|
||||
|
|
|
@ -54,6 +54,7 @@ async function init(
|
|||
liveshareApiPromise: Promise<LiveShare | undefined>,
|
||||
showPRController: ShowPullRequest,
|
||||
reposManager: RepositoriesManager,
|
||||
createPrHelper: CreatePullRequestHelper
|
||||
): Promise<void> {
|
||||
context.subscriptions.push(Logger);
|
||||
Logger.appendLine('Git repository found, initializing review manager and pr tree view.');
|
||||
|
@ -146,8 +147,7 @@ async function init(
|
|||
|
||||
const activePrViewCoordinator = new WebviewViewCoordinator(context);
|
||||
context.subscriptions.push(activePrViewCoordinator);
|
||||
const createPrHelper = new CreatePullRequestHelper();
|
||||
context.subscriptions.push(createPrHelper);
|
||||
|
||||
let reviewManagerIndex = 0;
|
||||
const reviewManagers = reposManager.folderManagers.map(
|
||||
folderManager => new ReviewManager(reviewManagerIndex++, context, folderManager.repository, folderManager, telemetry, changesTree, tree, showPRController, activePrViewCoordinator, createPrHelper, git),
|
||||
|
@ -170,7 +170,7 @@ async function init(
|
|||
Logger.appendLine(`Repo ${repo.rootUri} has already been setup.`);
|
||||
return;
|
||||
}
|
||||
const newFolderManager = new FolderRepositoryManager(reposManager.folderManagers.length, context, repo, telemetry, git, credentialStore);
|
||||
const newFolderManager = new FolderRepositoryManager(reposManager.folderManagers.length, context, repo, telemetry, git, credentialStore, createPrHelper);
|
||||
reposManager.insertFolderManager(newFolderManager);
|
||||
const newReviewManager = new ReviewManager(
|
||||
reviewManagerIndex++,
|
||||
|
@ -364,10 +364,12 @@ async function deferredActivate(context: vscode.ExtensionContext, apiImpl: GitAp
|
|||
Logger.appendLine('Looking for git repository');
|
||||
const repositories = apiImpl.repositories;
|
||||
Logger.appendLine(`Found ${repositories.length} repositories during activation`);
|
||||
const createPrHelper = new CreatePullRequestHelper();
|
||||
context.subscriptions.push(createPrHelper);
|
||||
|
||||
let folderManagerIndex = 0;
|
||||
const folderManagers = repositories.map(
|
||||
repository => new FolderRepositoryManager(folderManagerIndex++, context, repository, telemetry, apiImpl, credentialStore),
|
||||
repository => new FolderRepositoryManager(folderManagerIndex++, context, repository, telemetry, apiImpl, credentialStore, createPrHelper),
|
||||
);
|
||||
context.subscriptions.push(...folderManagers);
|
||||
for (const folderManager of folderManagers) {
|
||||
|
@ -379,7 +381,7 @@ async function deferredActivate(context: vscode.ExtensionContext, apiImpl: GitAp
|
|||
readOnlyMessage.isTrusted = { enabledCommands: ['pr.checkoutFromReadonlyFile'] };
|
||||
context.subscriptions.push(vscode.workspace.registerFileSystemProvider(Schemes.Pr, inMemPRFileSystemProvider, { isReadonly: readOnlyMessage }));
|
||||
|
||||
await init(context, apiImpl, credentialStore, repositories, prTree, liveshareApiPromise, showPRController, reposManager);
|
||||
await init(context, apiImpl, credentialStore, repositories, prTree, liveshareApiPromise, showPRController, reposManager, createPrHelper);
|
||||
}
|
||||
|
||||
export async function deactivate() {
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -33,6 +33,7 @@ import { batchPromiseAll, compareIgnoreCase, formatError, Predicate } from '../c
|
|||
import { PULL_REQUEST_OVERVIEW_VIEW_TYPE } from '../common/webview';
|
||||
import { NEVER_SHOW_PULL_NOTIFICATION, REPO_KEYS, ReposState } from '../extensionState';
|
||||
import { git } from '../gitProviders/gitCommands';
|
||||
import { CreatePullRequestHelper } from '../view/createPullRequestHelper';
|
||||
import { OctokitCommon } from './common';
|
||||
import { ConflictModel } from './conflictGuide';
|
||||
import { ConflictResolutionCoordinator } from './conflictResolutionCoordinator';
|
||||
|
@ -217,6 +218,7 @@ export class FolderRepositoryManager implements vscode.Disposable {
|
|||
public readonly telemetry: ITelemetry,
|
||||
private _git: GitApiImpl,
|
||||
private _credentialStore: CredentialStore,
|
||||
public readonly createPullRequestHelper: CreatePullRequestHelper
|
||||
) {
|
||||
this._subs = [];
|
||||
this._githubRepositories = [];
|
||||
|
@ -1954,6 +1956,18 @@ export class FolderRepositoryManager implements vscode.Disposable {
|
|||
});
|
||||
}
|
||||
|
||||
async revert(pullRequest: PullRequestModel, title: string, body: string, draft: boolean): Promise<PullRequestModel | undefined> {
|
||||
const repo = this._githubRepositories.find(
|
||||
r => r.remote.owner === pullRequest.remote.owner && r.remote.repositoryName === pullRequest.remote.repositoryName,
|
||||
);
|
||||
if (!repo) {
|
||||
throw new Error(`No matching repository ${pullRequest.remote.repositoryName} found for ${pullRequest.remote.owner}`);
|
||||
}
|
||||
|
||||
const pullRequestModel: PullRequestModel | undefined = await repo.revertPullRequest(pullRequest.graphNodeId, title, body, draft);
|
||||
return pullRequestModel;
|
||||
}
|
||||
|
||||
async getPullRequestRepositoryDefaultBranch(issue: IssueModel): Promise<string> {
|
||||
const branch = await issue.githubRepository.getDefaultBranch();
|
||||
return branch;
|
||||
|
|
|
@ -36,6 +36,7 @@ import {
|
|||
PullRequestsResponse,
|
||||
PullRequestTemplatesResponse,
|
||||
RepoProjectsResponse,
|
||||
RevertPullRequestResponse,
|
||||
ViewerPermissionResponse,
|
||||
} from './graphql';
|
||||
import {
|
||||
|
@ -180,7 +181,7 @@ export class GitHubRepository implements vscode.Disposable {
|
|||
`github-browse-${this.remote.normalizedHost}-${this.remote.owner}-${this.remote.repositoryName}`,
|
||||
`Pull Request (${this.remote.owner}/${this.remote.repositoryName})`,
|
||||
);
|
||||
this.commentsHandler = new PRCommentControllerRegistry(this.commentsController);
|
||||
this.commentsHandler = new PRCommentControllerRegistry(this.commentsController, this._telemetry);
|
||||
this._toDispose.push(this.commentsHandler);
|
||||
this._toDispose.push(this.commentsController);
|
||||
} catch (e) {
|
||||
|
@ -986,6 +987,33 @@ export class GitHubRepository implements vscode.Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
async revertPullRequest(pullRequestId: string, title: string, body: string, draft: boolean): Promise<PullRequestModel> {
|
||||
try {
|
||||
Logger.debug(`Revert pull request - enter`, this.id);
|
||||
const { mutate, schema } = await this.ensure();
|
||||
|
||||
const { data } = await mutate<RevertPullRequestResponse>({
|
||||
mutation: schema.RevertPullRequest,
|
||||
variables: {
|
||||
input: {
|
||||
pullRequestId,
|
||||
title,
|
||||
body,
|
||||
draft
|
||||
}
|
||||
}
|
||||
});
|
||||
Logger.debug(`Revert pull request - done`, this.id);
|
||||
if (!data) {
|
||||
throw new Error('Failed to create revert pull request.');
|
||||
}
|
||||
return this.createOrUpdatePullRequestModel(parseGraphQLPullRequest(data.revertPullRequest.revertPullRequest, this));
|
||||
} catch (e) {
|
||||
Logger.error(`Unable to create revert PR: ${e}`, this.id);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async getPullRequest(id: number): Promise<PullRequestModel | undefined> {
|
||||
try {
|
||||
Logger.debug(`Fetch pull request ${id} - enter`, this.id);
|
||||
|
|
|
@ -368,6 +368,12 @@ export interface CreatePullRequestResponse {
|
|||
}
|
||||
}
|
||||
|
||||
export interface RevertPullRequestResponse {
|
||||
revertPullRequest: {
|
||||
revertPullRequest: PullRequest
|
||||
}
|
||||
}
|
||||
|
||||
export interface AddReviewThreadResponse {
|
||||
addPullRequestReviewThread: {
|
||||
thread: ReviewThread;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { IComment } from '../common/comment';
|
||||
import Logger from '../common/logger';
|
||||
import { ITelemetry } from '../common/telemetry';
|
||||
import { asPromise, formatError } from '../common/utils';
|
||||
import { getNonce, IRequestMessage, WebviewBase } from '../common/webview';
|
||||
import { DescriptionNode } from '../view/treeNodes/descriptionNode';
|
||||
|
@ -32,6 +33,7 @@ export class IssueOverviewPanel<TItem extends IssueModel = IssueModel> extends W
|
|||
protected _scrollPosition = { x: 0, y: 0 };
|
||||
|
||||
public static async createOrShow(
|
||||
telemetry: ITelemetry,
|
||||
extensionUri: vscode.Uri,
|
||||
folderRepositoryManager: FolderRepositoryManager,
|
||||
issue: IssueModel,
|
||||
|
@ -50,6 +52,7 @@ export class IssueOverviewPanel<TItem extends IssueModel = IssueModel> extends W
|
|||
} else {
|
||||
const title = `Issue #${issue.number.toString()}`;
|
||||
IssueOverviewPanel.currentPanel = new IssueOverviewPanel(
|
||||
telemetry,
|
||||
extensionUri,
|
||||
activeColumn || vscode.ViewColumn.Active,
|
||||
title,
|
||||
|
@ -76,7 +79,8 @@ export class IssueOverviewPanel<TItem extends IssueModel = IssueModel> extends W
|
|||
}
|
||||
|
||||
protected constructor(
|
||||
private readonly _extensionUri: vscode.Uri,
|
||||
protected readonly _telemetry: ITelemetry,
|
||||
protected readonly _extensionUri: vscode.Uri,
|
||||
column: vscode.ViewColumn,
|
||||
title: string,
|
||||
folderRepositoryManager: FolderRepositoryManager,
|
||||
|
|
|
@ -10,6 +10,7 @@ import { IComment } from '../common/comment';
|
|||
import { commands, contexts } from '../common/executeCommands';
|
||||
import Logger from '../common/logger';
|
||||
import { DEFAULT_MERGE_METHOD, PR_SETTINGS_NAMESPACE } from '../common/settingKeys';
|
||||
import { ITelemetry } from '../common/telemetry';
|
||||
import { ReviewEvent as CommonReviewEvent } from '../common/timelineEvent';
|
||||
import { asPromise, dispose, formatError } from '../common/utils';
|
||||
import { IRequestMessage, PULL_REQUEST_OVERVIEW_VIEW_TYPE } from '../common/webview';
|
||||
|
@ -51,6 +52,7 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
|
|||
private _isUpdating: boolean = false;
|
||||
|
||||
public static async createOrShow(
|
||||
telemetry: ITelemetry,
|
||||
extensionUri: vscode.Uri,
|
||||
folderRepositoryManager: FolderRepositoryManager,
|
||||
issue: PullRequestModel,
|
||||
|
@ -70,10 +72,11 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
|
|||
} else {
|
||||
const title = `Pull Request #${issue.number.toString()}`;
|
||||
PullRequestOverviewPanel.currentPanel = new PullRequestOverviewPanel(
|
||||
telemetry,
|
||||
extensionUri,
|
||||
activeColumn || vscode.ViewColumn.Active,
|
||||
title,
|
||||
folderRepositoryManager,
|
||||
folderRepositoryManager
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -97,12 +100,13 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
|
|||
}
|
||||
|
||||
protected constructor(
|
||||
telemetry: ITelemetry,
|
||||
extensionUri: vscode.Uri,
|
||||
column: vscode.ViewColumn,
|
||||
title: string,
|
||||
folderRepositoryManager: FolderRepositoryManager,
|
||||
) {
|
||||
super(extensionUri, column, title, folderRepositoryManager, PULL_REQUEST_OVERVIEW_VIEW_TYPE);
|
||||
super(telemetry, extensionUri, column, title, folderRepositoryManager, PULL_REQUEST_OVERVIEW_VIEW_TYPE);
|
||||
|
||||
this.registerPrListeners();
|
||||
onDidUpdatePR(
|
||||
|
@ -303,7 +307,8 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
|
|||
isAuthor: currentUser.login === pullRequest.author.login,
|
||||
currentUserReviewState: reviewState,
|
||||
isDarkTheme: vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark,
|
||||
isEnterprise: pullRequest.githubRepository.remote.isEnterprise
|
||||
isEnterprise: pullRequest.githubRepository.remote.isEnterprise,
|
||||
revertable: pullRequest.state === GithubItemStateEnum.Merged
|
||||
};
|
||||
this._postMessage({
|
||||
command: 'pr.initialize',
|
||||
|
@ -400,11 +405,11 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
|
|||
case 'pr.update-branch':
|
||||
return this.updateBranch(message);
|
||||
case 'pr.gotoChangesSinceReview':
|
||||
this.gotoChangesSinceReview();
|
||||
break;
|
||||
return this.gotoChangesSinceReview();
|
||||
case 'pr.re-request-review':
|
||||
this.reRequestReview(message);
|
||||
break;
|
||||
return this.reRequestReview(message);
|
||||
case 'pr.revert':
|
||||
return this.revert(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -809,6 +814,13 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
|
|||
});
|
||||
}
|
||||
|
||||
private async revert(message: IRequestMessage<string>): Promise<void> {
|
||||
await this._folderRepositoryManager.createPullRequestHelper.revert(this._telemetry, this._extensionUri, this._folderRepositoryManager, this._item, async (pullRequest) => {
|
||||
const result: Partial<PullRequest> = { revertable: !pullRequest };
|
||||
return this._replyMessage(message, result);
|
||||
});
|
||||
}
|
||||
|
||||
private async copyPrLink(): Promise<void> {
|
||||
return vscode.env.clipboard.writeText(this._item.html_url);
|
||||
}
|
||||
|
|
|
@ -175,6 +175,14 @@ mutation CreatePullRequest($input: CreatePullRequestInput!) {
|
|||
}
|
||||
}
|
||||
|
||||
mutation RevertPullRequest($input: RevertPullRequestInput!) {
|
||||
revertPullRequest(input: $input) {
|
||||
revertPullRequest {
|
||||
...PullRequestFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Queries that only exist in this file and in extra
|
||||
|
||||
mutation DequeuePullRequest($input: DequeuePullRequestInput!) {
|
||||
|
|
|
@ -142,4 +142,12 @@ mutation CreatePullRequest($input: CreatePullRequestInput!) {
|
|||
...PullRequestFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutation RevertPullRequest($input: RevertPullRequestInput!) {
|
||||
revertPullRequest(input: $input) {
|
||||
revertPullRequest {
|
||||
...PullRequestFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { CreatePullRequestNew } from '../../common/views';
|
||||
import { openDescription } from '../commands';
|
||||
import { commands } from '../common/executeCommands';
|
||||
import { ITelemetry } from '../common/telemetry';
|
||||
import { IRequestMessage } from '../common/webview';
|
||||
import { BaseCreatePullRequestViewProvider, BasePullRequestDataModel } from './createPRViewProvider';
|
||||
import {
|
||||
FolderRepositoryManager,
|
||||
PullRequestDefaults,
|
||||
} from './folderRepositoryManager';
|
||||
import { BaseBranchMetadata } from './pullRequestGitHelper';
|
||||
import { PullRequestModel } from './pullRequestModel';
|
||||
|
||||
export class RevertPullRequestViewProvider extends BaseCreatePullRequestViewProvider implements vscode.WebviewViewProvider, vscode.Disposable {
|
||||
protected _canModifyBranches: boolean = false;
|
||||
|
||||
constructor(
|
||||
telemetry: ITelemetry,
|
||||
model: BasePullRequestDataModel,
|
||||
extensionUri: vscode.Uri,
|
||||
folderRepositoryManager: FolderRepositoryManager,
|
||||
pullRequestDefaults: PullRequestDefaults,
|
||||
private readonly pullRequest: PullRequestModel
|
||||
) {
|
||||
super(telemetry, model, extensionUri, folderRepositoryManager, pullRequestDefaults, pullRequest.base.name);
|
||||
}
|
||||
|
||||
protected async getTitleAndDescription(): Promise<{ title: string; description: string; }> {
|
||||
return {
|
||||
title: vscode.l10n.t('Revert "{0}"', this.pullRequest.title),
|
||||
description: vscode.l10n.t('Reverts {0}', `${this.pullRequest.remote.owner}/${this.pullRequest.remote.repositoryName}#${this.pullRequest.number}`)
|
||||
};
|
||||
}
|
||||
|
||||
protected async detectBaseMetadata(): Promise<BaseBranchMetadata | undefined> {
|
||||
return {
|
||||
owner: this.pullRequest.remote.owner,
|
||||
repositoryName: this.pullRequest.remote.repositoryName,
|
||||
branch: this.pullRequest.base.name
|
||||
};
|
||||
}
|
||||
|
||||
protected getTitleAndDescriptionProvider(_name?: string) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected async create(message: IRequestMessage<CreatePullRequestNew>): Promise<void> {
|
||||
let revertPr: PullRequestModel | undefined;
|
||||
RevertPullRequestViewProvider.withProgress(async () => {
|
||||
commands.setContext('pr:creating', true);
|
||||
try {
|
||||
revertPr = await this._folderRepositoryManager.revert(this.pullRequest, message.args.title, message.args.body, message.args.draft);
|
||||
if (revertPr) {
|
||||
await this.postCreate(message, revertPr);
|
||||
await openDescription(this.telemetry, revertPr, undefined, this._folderRepositoryManager, true);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
if (!revertPr) {
|
||||
let errorMessage: string = e.message;
|
||||
if (errorMessage.startsWith('GraphQL error: ')) {
|
||||
errorMessage = errorMessage.substring('GraphQL error: '.length);
|
||||
}
|
||||
this._throwError(message, errorMessage);
|
||||
} else {
|
||||
if ((e as Error).message === 'GraphQL error: ["Pull request Pull request is in unstable status"]') {
|
||||
// This error can happen if the PR isn't fully created by the time we try to set properties on it. Try again.
|
||||
await this.postCreate(message, revertPr);
|
||||
}
|
||||
// All of these errors occur after the PR is created, so the error is not critical.
|
||||
vscode.window.showErrorMessage(vscode.l10n.t('There was an error creating the pull request: {0}', (e as Error).message));
|
||||
}
|
||||
} finally {
|
||||
commands.setContext('pr:creating', false);
|
||||
if (revertPr) {
|
||||
this._onDone.fire(revertPr);
|
||||
} else {
|
||||
await this._replyMessage(message, {});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -89,6 +89,7 @@ export interface PullRequest {
|
|||
hasReviewDraft: boolean;
|
||||
|
||||
lastReviewType?: ReviewType;
|
||||
revertable?: boolean;
|
||||
busy?: boolean;
|
||||
}
|
||||
|
||||
|
@ -108,4 +109,4 @@ export enum PreReviewState {
|
|||
Available,
|
||||
ReviewedWithComments,
|
||||
ReviewedWithoutComments
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import { CredentialStore } from '../../github/credentials';
|
|||
import { MockExtensionContext } from '../mocks/mockExtensionContext';
|
||||
import { Uri } from 'vscode';
|
||||
import { GitHubServerType } from '../../common/authentication';
|
||||
import { CreatePullRequestHelper } from '../../view/createPullRequestHelper';
|
||||
|
||||
describe('PullRequestManager', function () {
|
||||
let sinon: SinonSandbox;
|
||||
|
@ -35,7 +36,7 @@ describe('PullRequestManager', function () {
|
|||
const repository = new MockRepository();
|
||||
const context = new MockExtensionContext();
|
||||
const credentialStore = new CredentialStore(telemetry, context);
|
||||
manager = new FolderRepositoryManager(0, context, repository, telemetry, new GitApiImpl(), credentialStore);
|
||||
manager = new FolderRepositoryManager(0, context, repository, telemetry, new GitApiImpl(), credentialStore, new CreatePullRequestHelper());
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
|
|
|
@ -23,6 +23,7 @@ import { CredentialStore } from '../../github/credentials';
|
|||
import { GitHubServerType } from '../../common/authentication';
|
||||
import { GitHubRemote } from '../../common/remote';
|
||||
import { CheckState } from '../../github/interface';
|
||||
import { CreatePullRequestHelper } from '../../view/createPullRequestHelper';
|
||||
|
||||
const EXTENSION_URI = vscode.Uri.joinPath(vscode.Uri.file(__dirname), '../../..');
|
||||
|
||||
|
@ -43,7 +44,8 @@ describe('PullRequestOverview', function () {
|
|||
const repository = new MockRepository();
|
||||
telemetry = new MockTelemetry();
|
||||
credentialStore = new CredentialStore(telemetry, context);
|
||||
pullRequestManager = new FolderRepositoryManager(0, context, repository, telemetry, new GitApiImpl(), credentialStore);
|
||||
const createPrHelper = new CreatePullRequestHelper();
|
||||
pullRequestManager = new FolderRepositoryManager(0, context, repository, telemetry, new GitApiImpl(), credentialStore, createPrHelper);
|
||||
|
||||
const url = 'https://github.com/aaa/bbb';
|
||||
remote = new GitHubRemote('origin', url, new Protocol(url), GitHubServerType.GitHubDotCom);
|
||||
|
@ -76,7 +78,7 @@ describe('PullRequestOverview', function () {
|
|||
const prItem = convertRESTPullRequestToRawPullRequest(new PullRequestBuilder().number(1000).build(), repo);
|
||||
const prModel = new PullRequestModel(credentialStore, telemetry, repo, remote, prItem);
|
||||
|
||||
await PullRequestOverviewPanel.createOrShow(EXTENSION_URI, pullRequestManager, prModel);
|
||||
await PullRequestOverviewPanel.createOrShow(telemetry, EXTENSION_URI, pullRequestManager, prModel);
|
||||
|
||||
assert(
|
||||
createWebviewPanel.calledWith(sinonMatch.string, 'Pull Request #1000', vscode.ViewColumn.One, {
|
||||
|
@ -112,7 +114,7 @@ describe('PullRequestOverview', function () {
|
|||
sinon.stub(prModel0, 'getReviewRequests').resolves([]);
|
||||
sinon.stub(prModel0, 'getTimelineEvents').resolves([]);
|
||||
sinon.stub(prModel0, 'getStatusChecks').resolves([{ state: CheckState.Success, statuses: [] }, null]);
|
||||
await PullRequestOverviewPanel.createOrShow(EXTENSION_URI, pullRequestManager, prModel0);
|
||||
await PullRequestOverviewPanel.createOrShow(telemetry, EXTENSION_URI, pullRequestManager, prModel0);
|
||||
|
||||
const panel0 = PullRequestOverviewPanel.currentPanel;
|
||||
assert.notStrictEqual(panel0, undefined);
|
||||
|
@ -125,7 +127,7 @@ describe('PullRequestOverview', function () {
|
|||
sinon.stub(prModel1, 'getReviewRequests').resolves([]);
|
||||
sinon.stub(prModel1, 'getTimelineEvents').resolves([]);
|
||||
sinon.stub(prModel1, 'getStatusChecks').resolves([{ state: CheckState.Success, statuses: [] }, null]);
|
||||
await PullRequestOverviewPanel.createOrShow(EXTENSION_URI, pullRequestManager, prModel1);
|
||||
await PullRequestOverviewPanel.createOrShow(telemetry, EXTENSION_URI, pullRequestManager, prModel1);
|
||||
|
||||
assert.strictEqual(panel0, PullRequestOverviewPanel.currentPanel);
|
||||
assert.strictEqual(createWebviewPanel.callCount, 1);
|
||||
|
|
|
@ -30,6 +30,7 @@ import { GitHubServerType } from '../../common/authentication';
|
|||
import { DataUri } from '../../common/uri';
|
||||
import { IAccount, ITeam } from '../../github/interface';
|
||||
import { asPromise } from '../../common/utils';
|
||||
import { CreatePullRequestHelper } from '../../view/createPullRequestHelper';
|
||||
|
||||
describe('GitHub Pull Requests view', function () {
|
||||
let sinon: SinonSandbox;
|
||||
|
@ -38,6 +39,8 @@ describe('GitHub Pull Requests view', function () {
|
|||
let provider: PullRequestsTreeDataProvider;
|
||||
let credentialStore: CredentialStore;
|
||||
let reposManager: RepositoriesManager;
|
||||
let createPrHelper: CreatePullRequestHelper;
|
||||
|
||||
|
||||
beforeEach(function () {
|
||||
sinon = createSandbox();
|
||||
|
@ -52,6 +55,7 @@ describe('GitHub Pull Requests view', function () {
|
|||
);
|
||||
provider = new PullRequestsTreeDataProvider(telemetry, context, reposManager);
|
||||
credentialStore = new CredentialStore(telemetry, context);
|
||||
createPrHelper = new CreatePullRequestHelper();
|
||||
|
||||
// For tree view unit tests, we don't test the authentication flow, so `showSignInNotification` returns
|
||||
// a dummy GitHub/Octokit object.
|
||||
|
@ -97,7 +101,7 @@ describe('GitHub Pull Requests view', function () {
|
|||
it('has no children when repositories have not yet been initialized', async function () {
|
||||
const repository = new MockRepository();
|
||||
repository.addRemote('origin', 'git@github.com:aaa/bbb');
|
||||
reposManager.insertFolderManager(new FolderRepositoryManager(0, context, repository, telemetry, new GitApiImpl(), credentialStore));
|
||||
reposManager.insertFolderManager(new FolderRepositoryManager(0, context, repository, telemetry, new GitApiImpl(), credentialStore, createPrHelper));
|
||||
provider.initialize([], credentialStore);
|
||||
|
||||
const rootNodes = await provider.getChildren();
|
||||
|
@ -107,7 +111,7 @@ describe('GitHub Pull Requests view', function () {
|
|||
it('opens the viewlet and displays the default categories', async function () {
|
||||
const repository = new MockRepository();
|
||||
repository.addRemote('origin', 'git@github.com:aaa/bbb');
|
||||
reposManager.insertFolderManager(new FolderRepositoryManager(0, context, repository, telemetry, new GitApiImpl(), credentialStore));
|
||||
reposManager.insertFolderManager(new FolderRepositoryManager(0, context, repository, telemetry, new GitApiImpl(), credentialStore, createPrHelper));
|
||||
sinon.stub(credentialStore, 'isAuthenticated').returns(true);
|
||||
await reposManager.folderManagers[0].updateRepositories();
|
||||
provider.initialize([], credentialStore);
|
||||
|
@ -175,7 +179,7 @@ describe('GitHub Pull Requests view', function () {
|
|||
|
||||
await repository.createBranch('non-pr-branch', false);
|
||||
|
||||
const manager = new FolderRepositoryManager(0, context, repository, telemetry, new GitApiImpl(), credentialStore);
|
||||
const manager = new FolderRepositoryManager(0, context, repository, telemetry, new GitApiImpl(), credentialStore, createPrHelper);
|
||||
reposManager.insertFolderManager(manager);
|
||||
sinon.stub(manager, 'createGitHubRepository').callsFake((r, cs) => {
|
||||
assert.deepStrictEqual(r, remote);
|
||||
|
|
|
@ -77,7 +77,7 @@ describe('ReviewCommentController', function () {
|
|||
const createPrHelper = new CreatePullRequestHelper();
|
||||
Resource.initialize(context);
|
||||
gitApiImpl = new GitApiImpl();
|
||||
manager = new FolderRepositoryManager(0, context, repository, telemetry, gitApiImpl, credentialStore);
|
||||
manager = new FolderRepositoryManager(0, context, repository, telemetry, gitApiImpl, credentialStore, createPrHelper);
|
||||
reposManager.insertFolderManager(manager);
|
||||
const tree = new PullRequestChangesTreeDataProvider(context, gitApiImpl, reposManager);
|
||||
reviewManager = new ReviewManager(0, context, repository, manager, telemetry, tree, provider, new ShowPullRequest(), activePrViewCoordinator, createPrHelper, gitApiImpl);
|
||||
|
@ -172,7 +172,7 @@ describe('ReviewCommentController', function () {
|
|||
const localFileChanges = [createLocalFileChange(uri, fileName, repository.rootUri)];
|
||||
const reviewModel = new ReviewModel();
|
||||
reviewModel.localFileChanges = localFileChanges;
|
||||
const reviewCommentController = new TestReviewCommentController(reviewManager, manager, repository, reviewModel, gitApiImpl);
|
||||
const reviewCommentController = new TestReviewCommentController(reviewManager, manager, repository, reviewModel, gitApiImpl, telemetry);
|
||||
|
||||
sinon.stub(activePullRequest, 'validateDraftMode').returns(Promise.resolve(false));
|
||||
sinon.stub(activePullRequest, 'getReviewThreads').returns(
|
||||
|
@ -237,7 +237,8 @@ describe('ReviewCommentController', function () {
|
|||
manager,
|
||||
repository,
|
||||
reviewModel,
|
||||
gitApiImpl
|
||||
gitApiImpl,
|
||||
telemetry
|
||||
);
|
||||
const thread = createGHPRCommentThread('review-1.1', uri);
|
||||
|
||||
|
|
|
@ -4,13 +4,15 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { ITelemetry } from '../common/telemetry';
|
||||
import { FolderRepositoryManager } from '../github/folderRepositoryManager';
|
||||
import { GitHubRepository } from '../github/githubRepository';
|
||||
import { PullRequestModel } from '../github/pullRequestModel';
|
||||
|
||||
export abstract class CommentControllerBase {
|
||||
constructor(
|
||||
protected _folderRepoManager: FolderRepositoryManager
|
||||
protected _folderRepoManager: FolderRepositoryManager,
|
||||
protected _telemetry: ITelemetry
|
||||
) { }
|
||||
|
||||
protected _commentController: vscode.CommentController;
|
||||
|
|
|
@ -7,45 +7,42 @@ import * as vscode from 'vscode';
|
|||
import { Repository } from '../api/api';
|
||||
import { ITelemetry } from '../common/telemetry';
|
||||
import { dispose } from '../common/utils';
|
||||
import { CreatePullRequestViewProvider } from '../github/createPRViewProvider';
|
||||
import { BaseCreatePullRequestViewProvider, BasePullRequestDataModel, CreatePullRequestViewProvider } from '../github/createPRViewProvider';
|
||||
import { FolderRepositoryManager, PullRequestDefaults } from '../github/folderRepositoryManager';
|
||||
import { PullRequestModel } from '../github/pullRequestModel';
|
||||
import { RevertPullRequestViewProvider } from '../github/revertPRViewProvider';
|
||||
import { CompareChanges } from './compareChangesTreeDataProvider';
|
||||
import { CreatePullRequestDataModel } from './createPullRequestDataModel';
|
||||
|
||||
export class CreatePullRequestHelper implements vscode.Disposable {
|
||||
private _disposables: vscode.Disposable[] = [];
|
||||
private _createPRViewProvider: CreatePullRequestViewProvider | undefined;
|
||||
private _createPRViewProvider: BaseCreatePullRequestViewProvider | undefined;
|
||||
private _treeView: CompareChanges | undefined;
|
||||
private _postCreateCallback: ((pullRequestModel: PullRequestModel) => Promise<void>) | undefined;
|
||||
private _postCreateCallback: ((pullRequestModel: PullRequestModel | undefined) => Promise<void>) | undefined;
|
||||
|
||||
constructor() { }
|
||||
|
||||
private registerListeners(repository: Repository, usingCurrentBranchAsCompare: boolean) {
|
||||
private registerListeners(repository: Repository, usingCurrentBranchAsCompare: boolean, activeContext: string) {
|
||||
this._disposables.push(
|
||||
this._createPRViewProvider!.onDone(async createdPR => {
|
||||
if (createdPR) {
|
||||
await CreatePullRequestViewProvider.withProgress(async () => {
|
||||
return this._postCreateCallback?.(createdPR);
|
||||
});
|
||||
}
|
||||
vscode.commands.executeCommand('setContext', activeContext, false);
|
||||
await CreatePullRequestViewProvider.withProgress(async () => {
|
||||
return this._postCreateCallback?.(createdPR);
|
||||
});
|
||||
this.dispose();
|
||||
}),
|
||||
);
|
||||
|
||||
this._disposables.push(
|
||||
vscode.commands.registerCommand('pr.addAssigneesToNewPr', _ => {
|
||||
if (this._createPRViewProvider instanceof CreatePullRequestViewProvider) {
|
||||
return this._createPRViewProvider.addAssignees();
|
||||
}
|
||||
return this._createPRViewProvider?.addAssignees();
|
||||
|
||||
}),
|
||||
);
|
||||
|
||||
this._disposables.push(
|
||||
vscode.commands.registerCommand('pr.addReviewersToNewPr', _ => {
|
||||
if (this._createPRViewProvider instanceof CreatePullRequestViewProvider) {
|
||||
return this._createPRViewProvider.addReviewers();
|
||||
}
|
||||
return this._createPRViewProvider?.addReviewers();
|
||||
}),
|
||||
);
|
||||
|
||||
|
@ -57,60 +54,50 @@ export class CreatePullRequestHelper implements vscode.Disposable {
|
|||
|
||||
this._disposables.push(
|
||||
vscode.commands.registerCommand('pr.addMilestoneToNewPr', _ => {
|
||||
if (this._createPRViewProvider instanceof CreatePullRequestViewProvider) {
|
||||
return this._createPRViewProvider.addMilestone();
|
||||
}
|
||||
return this._createPRViewProvider?.addMilestone();
|
||||
|
||||
}),
|
||||
);
|
||||
|
||||
this._disposables.push(
|
||||
vscode.commands.registerCommand('pr.addProjectsToNewPr', _ => {
|
||||
if (this._createPRViewProvider instanceof CreatePullRequestViewProvider) {
|
||||
return this._createPRViewProvider.addProjects();
|
||||
}
|
||||
return this._createPRViewProvider?.addProjects();
|
||||
|
||||
}),
|
||||
);
|
||||
|
||||
this._disposables.push(
|
||||
vscode.commands.registerCommand('pr.createPrMenuCreate', () => {
|
||||
if (this._createPRViewProvider instanceof CreatePullRequestViewProvider) {
|
||||
this._createPRViewProvider.createFromCommand(false, false, undefined);
|
||||
}
|
||||
this._createPRViewProvider?.createFromCommand(false, false, undefined);
|
||||
|
||||
})
|
||||
);
|
||||
this._disposables.push(
|
||||
vscode.commands.registerCommand('pr.createPrMenuDraft', () => {
|
||||
if (this._createPRViewProvider instanceof CreatePullRequestViewProvider) {
|
||||
this._createPRViewProvider.createFromCommand(true, false, undefined);
|
||||
}
|
||||
this._createPRViewProvider?.createFromCommand(true, false, undefined);
|
||||
|
||||
})
|
||||
);
|
||||
this._disposables.push(
|
||||
vscode.commands.registerCommand('pr.createPrMenuMergeWhenReady', () => {
|
||||
if (this._createPRViewProvider instanceof CreatePullRequestViewProvider) {
|
||||
this._createPRViewProvider.createFromCommand(false, true, undefined, true);
|
||||
}
|
||||
this._createPRViewProvider?.createFromCommand(false, true, undefined, true);
|
||||
|
||||
})
|
||||
);
|
||||
this._disposables.push(
|
||||
vscode.commands.registerCommand('pr.createPrMenuMerge', () => {
|
||||
if (this._createPRViewProvider instanceof CreatePullRequestViewProvider) {
|
||||
this._createPRViewProvider.createFromCommand(false, true, 'merge');
|
||||
}
|
||||
this._createPRViewProvider?.createFromCommand(false, true, 'merge');
|
||||
|
||||
})
|
||||
);
|
||||
this._disposables.push(
|
||||
vscode.commands.registerCommand('pr.createPrMenuSquash', () => {
|
||||
if (this._createPRViewProvider instanceof CreatePullRequestViewProvider) {
|
||||
this._createPRViewProvider.createFromCommand(false, true, 'squash');
|
||||
}
|
||||
this._createPRViewProvider?.createFromCommand(false, true, 'squash');
|
||||
})
|
||||
);
|
||||
this._disposables.push(
|
||||
vscode.commands.registerCommand('pr.createPrMenuRebase', () => {
|
||||
if (this._createPRViewProvider instanceof CreatePullRequestViewProvider) {
|
||||
this._createPRViewProvider.createFromCommand(false, true, 'rebase');
|
||||
}
|
||||
this._createPRViewProvider?.createFromCommand(false, true, 'rebase');
|
||||
})
|
||||
);
|
||||
this._disposables.push(
|
||||
|
@ -124,7 +111,7 @@ export class CreatePullRequestHelper implements vscode.Disposable {
|
|||
if (usingCurrentBranchAsCompare) {
|
||||
this._disposables.push(
|
||||
repository.state.onDidChange(_ => {
|
||||
if (this._createPRViewProvider && repository.state.HEAD) {
|
||||
if (this._createPRViewProvider && repository.state.HEAD && this._createPRViewProvider instanceof CreatePullRequestViewProvider) {
|
||||
this._createPRViewProvider.setDefaultCompareBranch(repository.state.HEAD);
|
||||
}
|
||||
}),
|
||||
|
@ -158,24 +145,69 @@ export class CreatePullRequestHelper implements vscode.Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
async create(
|
||||
async revert(
|
||||
telemetry: ITelemetry,
|
||||
extensionUri: vscode.Uri,
|
||||
folderRepoManager: FolderRepositoryManager,
|
||||
compareBranch: string | undefined,
|
||||
callback: (pullRequestModel: PullRequestModel) => Promise<void>,
|
||||
pullRequestModel: PullRequestModel,
|
||||
callback: (pullRequest: PullRequestModel | undefined) => Promise<void>,
|
||||
) {
|
||||
this.reset();
|
||||
|
||||
this._postCreateCallback = callback;
|
||||
await folderRepoManager.loginAndUpdate();
|
||||
vscode.commands.executeCommand('setContext', 'github:createPullRequest', true);
|
||||
const activeContext = 'github:revertPullRequest';
|
||||
vscode.commands.executeCommand('setContext', activeContext, true);
|
||||
|
||||
if (!this._createPRViewProvider || !(this._createPRViewProvider instanceof RevertPullRequestViewProvider)) {
|
||||
this._createPRViewProvider?.dispose();
|
||||
const model: BasePullRequestDataModel = {
|
||||
baseOwner: pullRequestModel.remote.owner,
|
||||
repositoryName: pullRequestModel.remote.repositoryName
|
||||
};
|
||||
this._createPRViewProvider = new RevertPullRequestViewProvider(
|
||||
telemetry,
|
||||
model,
|
||||
extensionUri,
|
||||
folderRepoManager,
|
||||
{ base: pullRequestModel.base.name, owner: pullRequestModel.remote.owner, repo: pullRequestModel.remote.repositoryName },
|
||||
pullRequestModel
|
||||
);
|
||||
|
||||
this.registerListeners(folderRepoManager.repository, false, activeContext);
|
||||
|
||||
this._disposables.push(
|
||||
vscode.window.registerWebviewViewProvider(
|
||||
this._createPRViewProvider.viewType,
|
||||
this._createPRViewProvider,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
this._createPRViewProvider.show();
|
||||
}
|
||||
|
||||
async create(
|
||||
telemetry: ITelemetry,
|
||||
extensionUri: vscode.Uri,
|
||||
folderRepoManager: FolderRepositoryManager,
|
||||
compareBranch: string | undefined,
|
||||
callback: (pullRequestModel: PullRequestModel | undefined) => Promise<void>,
|
||||
) {
|
||||
this.reset();
|
||||
|
||||
this._postCreateCallback = callback;
|
||||
await folderRepoManager.loginAndUpdate();
|
||||
const activeContext = 'github:createPullRequest';
|
||||
vscode.commands.executeCommand('setContext', activeContext, true);
|
||||
|
||||
const branch =
|
||||
((compareBranch ? await folderRepoManager.repository.getBranch(compareBranch) : undefined) ??
|
||||
folderRepoManager.repository.state.HEAD?.name ? folderRepoManager.repository.state.HEAD : undefined);
|
||||
|
||||
if (!this._createPRViewProvider) {
|
||||
let createViewProvider: CreatePullRequestViewProvider;
|
||||
if (!this._createPRViewProvider || !(this._createPRViewProvider instanceof CreatePullRequestViewProvider)) {
|
||||
this._createPRViewProvider?.dispose();
|
||||
const pullRequestDefaults = await this.ensureDefaultsAreLocal(
|
||||
folderRepoManager,
|
||||
await folderRepoManager.getPullRequestDefaults(branch),
|
||||
|
@ -183,7 +215,7 @@ export class CreatePullRequestHelper implements vscode.Disposable {
|
|||
|
||||
const compareOrigin = await folderRepoManager.getOrigin(branch);
|
||||
const model = new CreatePullRequestDataModel(folderRepoManager, pullRequestDefaults.owner, pullRequestDefaults.base, compareOrigin.remote.owner, branch?.name ?? pullRequestDefaults.base, compareOrigin.remote.repositoryName);
|
||||
this._createPRViewProvider = new CreatePullRequestViewProvider(
|
||||
createViewProvider = this._createPRViewProvider = new CreatePullRequestViewProvider(
|
||||
telemetry,
|
||||
model,
|
||||
extensionUri,
|
||||
|
@ -196,7 +228,7 @@ export class CreatePullRequestHelper implements vscode.Disposable {
|
|||
model
|
||||
);
|
||||
|
||||
this.registerListeners(folderRepoManager.repository, !compareBranch);
|
||||
this.registerListeners(folderRepoManager.repository, !compareBranch, activeContext);
|
||||
|
||||
this._disposables.push(
|
||||
vscode.window.registerWebviewViewProvider(
|
||||
|
@ -204,9 +236,11 @@ export class CreatePullRequestHelper implements vscode.Disposable {
|
|||
this._createPRViewProvider,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
createViewProvider = this._createPRViewProvider;
|
||||
}
|
||||
|
||||
this._createPRViewProvider.show(branch);
|
||||
createViewProvider.show(branch);
|
||||
}
|
||||
|
||||
private reset() {
|
||||
|
|
|
@ -8,6 +8,7 @@ import { v4 as uuid } from 'uuid';
|
|||
import * as vscode from 'vscode';
|
||||
import { CommentHandler, registerCommentHandler, unregisterCommentHandler } from '../commentHandlerResolver';
|
||||
import { DiffSide, IComment, SubjectType } from '../common/comment';
|
||||
import { ITelemetry } from '../common/telemetry';
|
||||
import { fromPRUri, Schemes } from '../common/uri';
|
||||
import { dispose, groupBy } from '../common/utils';
|
||||
import { FolderRepositoryManager } from '../github/folderRepositoryManager';
|
||||
|
@ -38,8 +39,9 @@ export class PullRequestCommentController extends CommentControllerBase implemen
|
|||
private readonly pullRequestModel: PullRequestModel,
|
||||
folderRepoManager: FolderRepositoryManager,
|
||||
commentController: vscode.CommentController,
|
||||
telemetry: ITelemetry
|
||||
) {
|
||||
super(folderRepoManager);
|
||||
super(folderRepoManager, telemetry);
|
||||
this._commentController = commentController;
|
||||
this._context = folderRepoManager.context;
|
||||
this._commentHandlerId = uuid();
|
||||
|
@ -454,7 +456,7 @@ export class PullRequestCommentController extends CommentControllerBase implemen
|
|||
|
||||
|
||||
public async openReview(): Promise<void> {
|
||||
await PullRequestOverviewPanel.createOrShow(this._folderRepoManager.context.extensionUri, this._folderRepoManager, this.pullRequestModel);
|
||||
await PullRequestOverviewPanel.createOrShow(this._telemetry, this._folderRepoManager.context.extensionUri, this._folderRepoManager, this.pullRequestModel);
|
||||
PullRequestOverviewPanel.scrollToReview();
|
||||
|
||||
/* __GDPR__
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { ITelemetry } from '../common/telemetry';
|
||||
import { fromPRUri, Schemes } from '../common/uri';
|
||||
import { FolderRepositoryManager } from '../github/folderRepositoryManager';
|
||||
import { GHPRComment } from '../github/prComment';
|
||||
|
@ -24,7 +25,7 @@ export class PRCommentControllerRegistry implements vscode.CommentingRangeProvid
|
|||
private _activeChangeListeners: Map<FolderRepositoryManager, vscode.Disposable> = new Map();
|
||||
public readonly resourceHints = { schemes: [Schemes.Pr] };
|
||||
|
||||
constructor(public commentsController: vscode.CommentController) {
|
||||
constructor(public commentsController: vscode.CommentController, private _telemetry: ITelemetry) {
|
||||
this.commentsController.commentingRangeProvider = this;
|
||||
this.commentsController.reactionHandler = this.toggleReaction.bind(this);
|
||||
}
|
||||
|
@ -84,7 +85,7 @@ export class PRCommentControllerRegistry implements vscode.CommentingRangeProvid
|
|||
}));
|
||||
}
|
||||
|
||||
const handler = new PullRequestCommentController(pullRequestModel, folderRepositoryManager, this.commentsController);
|
||||
const handler = new PullRequestCommentController(pullRequestModel, folderRepositoryManager, this.commentsController, this._telemetry);
|
||||
this._prCommentHandlers[prNumber] = {
|
||||
handler,
|
||||
refCount: 1,
|
||||
|
|
|
@ -15,6 +15,7 @@ import { mapNewPositionToOld, mapOldPositionToNew } from '../common/diffPosition
|
|||
import { GitChangeType } from '../common/file';
|
||||
import Logger from '../common/logger';
|
||||
import { PR_SETTINGS_NAMESPACE, PULL_BRANCH, PULL_PR_BRANCH_BEFORE_CHECKOUT, PullPRBranchVariants } from '../common/settingKeys';
|
||||
import { ITelemetry } from '../common/telemetry';
|
||||
import { fromReviewUri, ReviewUriParams, Schemes, toReviewUri } from '../common/uri';
|
||||
import { dispose, formatError, groupBy, uniqBy } from '../common/utils';
|
||||
import { FolderRepositoryManager } from '../github/folderRepositoryManager';
|
||||
|
@ -59,9 +60,10 @@ export class ReviewCommentController extends CommentControllerBase
|
|||
folderRepoManager: FolderRepositoryManager,
|
||||
private _repository: Repository,
|
||||
private _reviewModel: ReviewModel,
|
||||
private _gitApi: GitApiImpl
|
||||
private _gitApi: GitApiImpl,
|
||||
telemetry: ITelemetry
|
||||
) {
|
||||
super(folderRepoManager);
|
||||
super(folderRepoManager, telemetry);
|
||||
this._context = this._folderRepoManager.context;
|
||||
this._commentController = vscode.comments.createCommentController(
|
||||
`github-review-${folderRepoManager.activePullRequest?.remote.owner}-${folderRepoManager.activePullRequest?.remote.owner}-${folderRepoManager.activePullRequest!.number}`,
|
||||
|
|
|
@ -905,7 +905,8 @@ export class ReviewManager {
|
|||
this._folderRepoManager,
|
||||
this._repository,
|
||||
this._reviewModel,
|
||||
this._gitApi
|
||||
this._gitApi,
|
||||
this._telemetry
|
||||
);
|
||||
|
||||
await this._reviewCommentController.initialize();
|
||||
|
@ -1149,12 +1150,15 @@ export class ReviewManager {
|
|||
}
|
||||
|
||||
public async createPullRequest(compareBranch?: string): Promise<void> {
|
||||
const postCreate = async (createdPR: PullRequestModel) => {
|
||||
const postCreate = async (createdPR: PullRequestModel | undefined) => {
|
||||
if (!createdPR) {
|
||||
return;
|
||||
}
|
||||
|
||||
const postCreate = vscode.workspace.getConfiguration(PR_SETTINGS_NAMESPACE).get<'none' | 'openOverview' | 'checkoutDefaultBranch' | 'checkoutDefaultBranchAndShow' | 'checkoutDefaultBranchAndCopy'>(POST_CREATE, 'openOverview');
|
||||
if (postCreate === 'openOverview') {
|
||||
const descriptionNode = this.changesInPrDataProvider.getDescriptionNode(this._folderRepoManager);
|
||||
await openDescription(
|
||||
this._context,
|
||||
this._telemetry,
|
||||
createdPR,
|
||||
descriptionNode,
|
||||
|
@ -1192,7 +1196,6 @@ export class ReviewManager {
|
|||
|
||||
const descriptionNode = this.changesInPrDataProvider.getDescriptionNode(this._folderRepoManager);
|
||||
await openDescription(
|
||||
this._context,
|
||||
this._telemetry,
|
||||
pullRequest,
|
||||
descriptionNode,
|
||||
|
|
|
@ -18,7 +18,7 @@ export class DescriptionNode extends TreeNode implements vscode.TreeItem {
|
|||
constructor(
|
||||
public parent: TreeNodeParent,
|
||||
public label: string,
|
||||
public pullRequestModel: PullRequestModel,
|
||||
public readonly pullRequestModel: PullRequestModel,
|
||||
public readonly repository: Repository,
|
||||
private readonly folderRepositoryManager: FolderRepositoryManager
|
||||
) {
|
||||
|
|
|
@ -64,6 +64,12 @@ export class PRContext {
|
|||
|
||||
public deleteBranch = () => this.postMessage({ command: 'pr.deleteBranch' });
|
||||
|
||||
public revert = async () => {
|
||||
this.updatePR({ busy: true });
|
||||
const revertResult = await this.postMessage({ command: 'pr.revert' });
|
||||
this.updatePR({ busy: false, ...revertResult });
|
||||
};
|
||||
|
||||
public readyForReview = (): Promise<ReadyForReview> => this.postMessage({ command: 'pr.readyForReview' });
|
||||
|
||||
public addReviewers = () => this.postMessage({ command: 'pr.change-reviewers' });
|
||||
|
|
|
@ -5,10 +5,12 @@
|
|||
|
||||
import { createContext } from 'react';
|
||||
import { ChooseBaseRemoteAndBranchResult, ChooseCompareRemoteAndBranchResult, ChooseRemoteAndBranchArgs, CreateParamsNew, CreatePullRequestNew, RemoteInfo, ScrollPosition, TitleAndDescriptionArgs, TitleAndDescriptionResult } from '../../common/views';
|
||||
import { compareIgnoreCase } from '../../src/common/utils';
|
||||
import { PreReviewState } from '../../src/github/views';
|
||||
import { getMessageHandler, MessageHandler, vscode } from './message';
|
||||
|
||||
const defaultCreateParams: CreateParamsNew = {
|
||||
canModifyBranches: true,
|
||||
defaultBaseRemote: undefined,
|
||||
defaultBaseBranch: undefined,
|
||||
defaultCompareRemote: undefined,
|
||||
|
@ -49,7 +51,25 @@ export class CreatePRContextNew {
|
|||
}
|
||||
}
|
||||
|
||||
get isCreatable(): boolean {
|
||||
if (!this.createParams.canModifyBranches) {
|
||||
return true;
|
||||
}
|
||||
if (this.createParams.baseRemote && this.createParams.compareRemote && this.createParams.baseBranch && this.createParams.compareBranch
|
||||
&& compareIgnoreCase(this.createParams.baseRemote?.owner, this.createParams.compareRemote?.owner) === 0
|
||||
&& compareIgnoreCase(this.createParams.baseRemote?.repositoryName, this.createParams.compareRemote?.repositoryName) === 0
|
||||
&& compareIgnoreCase(this.createParams.baseBranch, this.createParams.compareBranch) === 0) {
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
get initialized(): boolean {
|
||||
if (!this.createParams.canModifyBranches) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.createParams.defaultBaseRemote !== undefined
|
||||
|| this.createParams.defaultBaseBranch !== undefined
|
||||
|| this.createParams.defaultCompareRemote !== undefined
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
} from '../../src/common/timelineEvent';
|
||||
import { groupBy, UnreachableCaseError } from '../../src/common/utils';
|
||||
import PullRequestContext from '../common/context';
|
||||
import { CommentView } from './comment';
|
||||
import { CommentView } from './comment';
|
||||
import Diff from './diff';
|
||||
import { commitIcon, mergeIcon, plusIcon } from './icon';
|
||||
import { nbsp } from './space';
|
||||
|
@ -26,26 +26,26 @@ import { AuthorLink, Avatar } from './user';
|
|||
|
||||
export const Timeline = ({ events }: { events: TimelineEvent[] }) => (
|
||||
<>
|
||||
{events.map(event => {
|
||||
switch (event.event) {
|
||||
case EventType.Committed:
|
||||
return <CommitEventView key={`commit${event.id}`} {...event} />;
|
||||
case EventType.Reviewed:
|
||||
return <ReviewEventView key={`review${event.id}`} {...event} />;
|
||||
case EventType.Commented:
|
||||
return <CommentEventView key={`comment${event.id}`} {...event} />;
|
||||
case EventType.Merged:
|
||||
return <MergedEventView key={`merged${event.id}`} {...event} />;
|
||||
case EventType.Assigned:
|
||||
return <AssignEventView key={`assign${event.id}`} {...event} />;
|
||||
case EventType.HeadRefDeleted:
|
||||
return <HeadDeleteEventView key={`head${event.id}`} {...event} />;
|
||||
case EventType.NewCommitsSinceReview:
|
||||
return <NewCommitsSinceReviewEventView key={`newCommits${event.id}`} />;
|
||||
default:
|
||||
throw new UnreachableCaseError(event);
|
||||
}
|
||||
})}
|
||||
{events.map(event => {
|
||||
switch (event.event) {
|
||||
case EventType.Committed:
|
||||
return <CommitEventView key={`commit${event.id}`} {...event} />;
|
||||
case EventType.Reviewed:
|
||||
return <ReviewEventView key={`review${event.id}`} {...event} />;
|
||||
case EventType.Commented:
|
||||
return <CommentEventView key={`comment${event.id}`} {...event} />;
|
||||
case EventType.Merged:
|
||||
return <MergedEventView key={`merged${event.id}`} {...event} />;
|
||||
case EventType.Assigned:
|
||||
return <AssignEventView key={`assign${event.id}`} {...event} />;
|
||||
case EventType.HeadRefDeleted:
|
||||
return <HeadDeleteEventView key={`head${event.id}`} {...event} />;
|
||||
case EventType.NewCommitsSinceReview:
|
||||
return <NewCommitsSinceReviewEventView key={`newCommits${event.id}`} />;
|
||||
default:
|
||||
throw new UnreachableCaseError(event);
|
||||
}
|
||||
})}
|
||||
</>
|
||||
);
|
||||
|
||||
|
@ -66,7 +66,7 @@ const CommitEventView = (event: CommitEvent) => (
|
|||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sha-with-timestamp">
|
||||
<div className="timeline-detail">
|
||||
<a className="sha" href={event.htmlUrl} title={event.htmlUrl}>
|
||||
{event.sha.slice(0, 7)}
|
||||
</a>
|
||||
|
@ -106,16 +106,16 @@ const ReviewEventView = (event: ReviewEvent) => {
|
|||
const reviewIsPending = event.state === 'PENDING';
|
||||
return (
|
||||
<CommentView comment={event} allowEmpty={true}>
|
||||
{/* Don't show the empty comment body unless a comment has been written. Shows diffs and suggested changes. */}
|
||||
{event.comments.length ? (
|
||||
<div className="comment-body review-comment-body">
|
||||
{Object.entries(comments).map(([key, thread]) => {
|
||||
return <CommentThread key={key} thread={thread} event={event} />;
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
{/* Don't show the empty comment body unless a comment has been written. Shows diffs and suggested changes. */}
|
||||
{event.comments.length ? (
|
||||
<div className="comment-body review-comment-body">
|
||||
{Object.entries(comments).map(([key, thread]) => {
|
||||
return <CommentThread key={key} thread={thread} event={event} />;
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{reviewIsPending ? <AddReviewSummaryComment /> : null}
|
||||
{reviewIsPending ? <AddReviewSummaryComment /> : null}
|
||||
</CommentView>
|
||||
);
|
||||
};
|
||||
|
@ -222,28 +222,36 @@ function AddReviewSummaryComment() {
|
|||
|
||||
const CommentEventView = (event: CommentEvent) => <CommentView headerInEditMode comment={event} />;
|
||||
|
||||
const MergedEventView = (event: MergedEvent) => (
|
||||
<div className="comment-container commit">
|
||||
<div className="commit-message">
|
||||
{mergeIcon}
|
||||
{nbsp}
|
||||
<div className="avatar-container">
|
||||
<Avatar for={event.user} />
|
||||
</div>
|
||||
<AuthorLink for={event.user} />
|
||||
<div className="message">
|
||||
merged commit{nbsp}
|
||||
<a className="sha" href={event.commitUrl} title={event.commitUrl}>
|
||||
{event.sha.substr(0, 7)}
|
||||
</a>
|
||||
{nbsp}
|
||||
into {event.mergeRef}
|
||||
const MergedEventView = (event: MergedEvent) => {
|
||||
const { revert, pr } = useContext(PullRequestContext);
|
||||
|
||||
return (
|
||||
<div className="comment-container commit">
|
||||
<div className="commit-message">
|
||||
{mergeIcon}
|
||||
{nbsp}
|
||||
<div className="avatar-container">
|
||||
<Avatar for={event.user} />
|
||||
</div>
|
||||
<AuthorLink for={event.user} />
|
||||
<div className="message">
|
||||
merged commit{nbsp}
|
||||
<a className="sha" href={event.commitUrl} title={event.commitUrl}>
|
||||
{event.sha.substr(0, 7)}
|
||||
</a>
|
||||
{nbsp}
|
||||
into {event.mergeRef}
|
||||
{nbsp}
|
||||
</div>
|
||||
<Timestamp href={event.url} date={event.createdAt} />
|
||||
</div>
|
||||
<Timestamp href={event.url} date={event.createdAt} />
|
||||
{pr.revertable ?
|
||||
<div className="timeline-detail">
|
||||
<button className='secondary' disabled={pr.busy} onClick={revert}>Revert</button>
|
||||
</div> : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
const HeadDeleteEventView = (event: HeadRefDeleteEvent) => (
|
||||
<div className="comment-container commit">
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { CreateParamsNew, RemoteInfo } from '../../common/views';
|
||||
import { compareIgnoreCase } from '../../src/common/utils';
|
||||
import { isTeam, MergeMethod } from '../../src/github/interface';
|
||||
import PullRequestContextNew from '../common/createContextNew';
|
||||
import { ErrorBoundary } from '../common/errorBoundary';
|
||||
|
@ -89,15 +88,6 @@ export function main() {
|
|||
setBusy(false);
|
||||
}
|
||||
|
||||
let isCreateable: boolean = true;
|
||||
if (ctx.createParams.baseRemote && ctx.createParams.compareRemote && ctx.createParams.baseBranch && ctx.createParams.compareBranch
|
||||
&& compareIgnoreCase(ctx.createParams.baseRemote?.owner, ctx.createParams.compareRemote?.owner) === 0
|
||||
&& compareIgnoreCase(ctx.createParams.baseRemote?.repositoryName, ctx.createParams.compareRemote?.repositoryName) === 0
|
||||
&& compareIgnoreCase(ctx.createParams.baseBranch, ctx.createParams.compareBranch) === 0) {
|
||||
|
||||
isCreateable = false;
|
||||
}
|
||||
|
||||
const onKeyDown = useCallback((isTitle: boolean, e) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
|
@ -198,7 +188,7 @@ export function main() {
|
|||
}
|
||||
|
||||
return <div className='group-main' data-vscode-context='{"preventDefaultContextMenuItems": true}'>
|
||||
<div className='group-branches'>
|
||||
<div className='group-branches' hidden={!ctx.createParams.canModifyBranches}>
|
||||
<div className='input-label base'>
|
||||
<div className="deco">
|
||||
<span title='Base branch' aria-hidden='true'>{prBaseIcon} Base</span>
|
||||
|
@ -357,7 +347,7 @@ export function main() {
|
|||
defaultOptionLabel={() => createMethodLabel(ctx.createParams.isDraft, ctx.createParams.autoMerge, ctx.createParams.autoMergeMethod, ctx.createParams.baseHasMergeQueue).label}
|
||||
defaultOptionValue={() => createMethodLabel(ctx.createParams.isDraft, ctx.createParams.autoMerge, ctx.createParams.autoMergeMethod, ctx.createParams.baseHasMergeQueue).value}
|
||||
optionsTitle='Create with Option'
|
||||
disabled={isBusy || isGeneratingTitle || params.reviewing || !isCreateable || !ctx.initialized}
|
||||
disabled={isBusy || isGeneratingTitle || params.reviewing || !ctx.isCreatable || !ctx.initialized}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -137,6 +137,7 @@ button.input-box {
|
|||
.group-description {
|
||||
flex-grow: 1;
|
||||
margin-top: 10px;
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
input[type=text],
|
||||
|
|
|
@ -658,7 +658,7 @@ body button .icon {
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sha-with-timestamp {
|
||||
.timeline-detail {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
|
Загрузка…
Ссылка в новой задаче