diff --git a/common/views.ts b/common/views.ts index 8690f5a4ab..a776749604 100644 --- a/common/views.ts +++ b/common/views.ts @@ -120,6 +120,7 @@ export interface CreateParamsNew { allowAutoMerge?: boolean; defaultMergeMethod?: MergeMethod; mergeMethodsAvailability?: MergeMethodsAvailability; + baseHasMergeQueue: boolean; creating: boolean; } @@ -137,6 +138,7 @@ export interface ChooseBaseRemoteAndBranchResult { allowAutoMerge: boolean; mergeMethodsAvailability: MergeMethodsAvailability; autoMergeDefault: boolean; + baseHasMergeQueue: boolean; defaultTitle: string; defaultDescription: string; } diff --git a/package.json b/package.json index 9aee6f93df..aa4d895ff9 100644 --- a/package.json +++ b/package.json @@ -1108,6 +1108,11 @@ "title": "%command.pr.createPrMenuDraft.title%", "category": "%command.pull.request.category%" }, + { + "command": "pr.createPrMenuMergeWhenReady", + "title": "%command.pr.createPrMenuMergeWhenReady.title%", + "category": "%command.pull.request.category%" + }, { "command": "pr.createPrMenuMerge", "title": "%command.pr.createPrMenuMerge.title%", @@ -1733,6 +1738,10 @@ "command": "pr.createPrMenuDraft", "when": "false" }, + { + "command": "pr.createPrMenuMergeWhenReady", + "when": "false" + }, { "command": "pr.createPrMenuMerge", "when": "false" @@ -2514,6 +2523,11 @@ "when": "webviewId == 'github:createPullRequestWebview' && github:createPrMenu && github:createPrMenuDraft", "group": "0_create@1" }, + { + "command": "pr.createPrMenuMergeWhenReady", + "when": "webviewId == 'github:createPullRequestWebview' && github:createPrMenu && github:createPrMenuMergeWhenReady", + "group": "1_create@0" + }, { "command": "pr.createPrMenuMerge", "when": "webviewId == 'github:createPullRequestWebview' && github:createPrMenu && github:createPrMenuMerge", diff --git a/package.nls.json b/package.nls.json index b0501396a1..fc0597b97c 100644 --- a/package.nls.json +++ b/package.nls.json @@ -217,6 +217,7 @@ "command.pr.createPrMenuCreate.title": "Create", "command.pr.createPrMenuDraft.title": "Create Draft", "command.pr.createPrMenuSquash.title": "Create + Auto-Squash", + "command.pr.createPrMenuMergeWhenReady.title": "Create + Merge When Ready", "command.pr.createPrMenuMerge.title": "Create + Auto-Merge", "command.pr.createPrMenuRebase.title": "Create + Auto-Rebase", "command.pr.refreshComments.title": "Refresh Pull Request Comments", diff --git a/src/github/createPRViewProviderNew.ts b/src/github/createPRViewProviderNew.ts index 6825812684..0d6b66e9f3 100644 --- a/src/github/createPRViewProviderNew.ts +++ b/src/github/createPRViewProviderNew.ts @@ -306,10 +306,11 @@ export class CreatePullRequestViewProviderNew extends WebviewViewBase implements this._onDidChangeBaseBranch.fire(defaultBaseBranch); } - const [defaultTitleAndDescription, mergeConfiguration, viewerPermission] = await Promise.all([ + const [defaultTitleAndDescription, mergeConfiguration, viewerPermission, mergeQueueMethodForBranch] = await Promise.all([ this.getTitleAndDescription(this.defaultCompareBranch, defaultBaseBranch), this.getMergeConfiguration(defaultBaseRemote.owner, defaultBaseRemote.repositoryName), - defaultOrigin.getViewerPermission() + defaultOrigin.getViewerPermission(), + this._folderRepositoryManager.mergeQueueMethodForBranch(defaultBaseBranch, defaultBaseRemote.owner, defaultBaseRemote.repositoryName) ]); const defaultCreateOption = vscode.workspace.getConfiguration(PR_SETTINGS_NAMESPACE).get<'lastUsed' | 'create' | 'createDraft' | 'createAutoMerge'>(DEFAULT_CREATE_OPTION, 'lastUsed'); @@ -352,6 +353,7 @@ export class CreatePullRequestViewProviderNew extends WebviewViewBase implements defaultTitle: defaultTitleAndDescription.title, defaultDescription: defaultTitleAndDescription.description, defaultMergeMethod, + baseHasMergeQueue: !!mergeQueueMethodForBranch, remoteCount: remotes.length, allowAutoMerge: mergeConfiguration.viewerCanAutoMerge, mergeMethodsAvailability: mergeConfiguration.mergeMethodsAvailability, @@ -438,7 +440,10 @@ export class CreatePullRequestViewProviderNew extends WebviewViewBase implements this._baseBranch = result.branch; this._baseRemote = result.remote; const compareBranch = await this._folderRepositoryManager.repository.getBranch(this._compareBranch); - const [mergeConfiguration, titleAndDescription] = await Promise.all([this.getMergeConfiguration(result.remote.owner, result.remote.repositoryName), this.getTitleAndDescription(compareBranch, this._baseBranch)]); + const [mergeConfiguration, titleAndDescription, mergeQueueMethodForBranch] = await Promise.all([ + this.getMergeConfiguration(result.remote.owner, result.remote.repositoryName), + this.getTitleAndDescription(compareBranch, this._baseBranch), + this._folderRepositoryManager.mergeQueueMethodForBranch(this._baseBranch, this._baseRemote.owner, this._baseRemote.repositoryName)]); let autoMergeDefault = false; if (mergeConfiguration.viewerCanAutoMerge) { const defaultCreateOption = vscode.workspace.getConfiguration(PR_SETTINGS_NAMESPACE).get<'lastUsed' | 'create' | 'createDraft' | 'createAutoMerge'>(DEFAULT_CREATE_OPTION, 'lastUsed'); @@ -452,6 +457,7 @@ export class CreatePullRequestViewProviderNew extends WebviewViewBase implements defaultBaseBranch: defaultBranch, defaultMergeMethod: getDefaultMergeMethod(mergeConfiguration.mergeMethodsAvailability), allowAutoMerge: mergeConfiguration.viewerCanAutoMerge, + baseHasMergeQueue: !!mergeQueueMethodForBranch, mergeMethodsAvailability: mergeConfiguration.mergeMethodsAvailability, autoMergeDefault, defaultTitle: titleAndDescription.title, @@ -794,11 +800,11 @@ export class CreatePullRequestViewProviderNew extends WebviewViewBase implements } } - public async createFromCommand(isDraft: boolean, autoMerge: boolean, autoMergeMethod: MergeMethod | undefined) { + public async createFromCommand(isDraft: boolean, autoMerge: boolean, autoMergeMethod: MergeMethod | undefined, mergeWhenReady?: boolean) { const params: Partial = { isDraft, autoMerge, - autoMergeMethod, + autoMergeMethod: mergeWhenReady ? 'merge' : autoMergeMethod, creating: true }; return this._postMessage({ diff --git a/src/view/createPullRequestHelper.ts b/src/view/createPullRequestHelper.ts index b83ab429cc..7317e8d88a 100644 --- a/src/view/createPullRequestHelper.ts +++ b/src/view/createPullRequestHelper.ts @@ -103,6 +103,13 @@ export class CreatePullRequestHelper implements vscode.Disposable { } }) ); + this._disposables.push( + vscode.commands.registerCommand('pr.createPrMenuMergeWhenReady', () => { + if (this._createPRViewProvider instanceof CreatePullRequestViewProviderNew) { + this._createPRViewProvider.createFromCommand(false, true, undefined, true); + } + }) + ); this._disposables.push( vscode.commands.registerCommand('pr.createPrMenuMerge', () => { if (this._createPRViewProvider instanceof CreatePullRequestViewProviderNew) { diff --git a/webviews/common/createContext.ts b/webviews/common/createContext.ts deleted file mode 100644 index 41b018b894..0000000000 --- a/webviews/common/createContext.ts +++ /dev/null @@ -1,281 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { createContext } from 'react'; -import { CreateParams, CreatePullRequest, ScrollPosition } from '../../common/views'; -import { getMessageHandler, MessageHandler, vscode } from './message'; - -const defaultCreateParams: CreateParams = { - availableBaseRemotes: [], - availableCompareRemotes: [], - branchesForRemote: [], - branchesForCompare: [], - validate: false, - showTitleValidationError: false, - labels: [], - isDraftDefault: false, - autoMergeDefault: false -}; - -export class CreatePRContext { - public createParams: CreateParams; - - constructor( - public onchange: ((ctx: CreateParams) => void) | null = null, - private _handler: MessageHandler | null = null, - ) { - this.createParams = vscode.getState() ?? defaultCreateParams; - if (!_handler) { - this._handler = getMessageHandler(this.handleMessage); - } - } - - get initialized(): boolean { - if (this.createParams.availableBaseRemotes.length !== 0 - || this.createParams.availableCompareRemotes.length !== 0 - || this.createParams.branchesForRemote.length !== 0 - || this.createParams.branchesForCompare.length !== 0 - || this.createParams.validate - || this.createParams.showTitleValidationError) { - return true; - } - - return false; - } - - public cancelCreate = (): Promise => { - const args = this.copyParams(); - vscode.setState(defaultCreateParams); - return this.postMessage({ command: 'pr.cancelCreate', args }); - }; - - public updateState = (params: Partial): void => { - this.createParams = { ...this.createParams, ...params }; - vscode.setState(this.createParams); - if (this.onchange) { - this.onchange(this.createParams); - } - }; - - public changeBaseRemote = async (owner: string, repositoryName: string): Promise => { - const response = await this.postMessage({ - command: 'pr.changeBaseRemote', - args: { - owner, - repositoryName, - }, - }); - - const updateValues: Partial = { - baseRemote: { owner, repositoryName }, - branchesForRemote: response.branches - }; - if ((this.createParams.baseRemote?.owner !== owner) || (this.createParams.baseRemote.repositoryName !== repositoryName)) { - updateValues.baseBranch = response.defaultBranch; - updateValues.defaultMergeMethod = response.defaultMergeMethod; - updateValues.allowAutoMerge = response.allowAutoMerge; - updateValues.mergeMethodsAvailability = response.mergeMethodsAvailability; - updateValues.autoMergeDefault = response.autoMergeDefault; - if (!this.createParams.allowAutoMerge && updateValues.allowAutoMerge) { - updateValues.autoMerge = this.createParams.isDraft ? false : updateValues.autoMergeDefault; - } - } - - this.updateState(updateValues); - }; - - public changeBaseBranch = async (branch: string): Promise => { - const response: { title?: string, description?: string } = await this.postMessage({ - command: 'pr.changeBaseBranch', - args: branch - }); - - const pendingTitle = ((this.createParams.pendingTitle === undefined) || (this.createParams.pendingTitle === this.createParams.defaultTitle)) - ? response.title : this.createParams.pendingTitle; - const pendingDescription = ((this.createParams.pendingDescription === undefined) || (this.createParams.pendingDescription === this.createParams.defaultDescription)) - ? response.description : this.createParams.pendingDescription; - - this.updateState({ - pendingTitle, - pendingDescription - }); - }; - - public changeCompareRemote = async (owner: string, repositoryName: string): Promise => { - const response = await this.postMessage({ - command: 'pr.changeCompareRemote', - args: { - owner, - repositoryName, - }, - }); - - const updateValues: Partial = { - compareRemote: { owner, repositoryName }, - branchesForCompare: response.branches - }; - if ((this.createParams.compareRemote?.owner !== owner) || (this.createParams.compareRemote.repositoryName !== repositoryName)) { - updateValues.compareBranch = response.defaultBranch; - } - - this.updateState(updateValues); - }; - - public changeCompareBranch = async (branch: string): Promise => { - return this.postMessage({ command: 'pr.changeCompareBranch', args: branch }); - }; - - public validate = (): boolean => { - let isValid = true; - if (!this.createParams.pendingTitle) { - this.updateState({ showTitleValidationError: true }); - isValid = false; - } - - this.updateState({ validate: true, createError: undefined }); - - return isValid; - }; - - private copyParams(): CreatePullRequest { - return { - title: this.createParams.pendingTitle!, - body: this.createParams.pendingDescription!, - owner: this.createParams.baseRemote!.owner, - repo: this.createParams.baseRemote!.repositoryName, - base: this.createParams.baseBranch!, - compareBranch: this.createParams.compareBranch!, - compareOwner: this.createParams.compareRemote!.owner, - compareRepo: this.createParams.compareRemote!.repositoryName, - draft: !!this.createParams.isDraft, - autoMerge: !!this.createParams.autoMerge, - autoMergeMethod: this.createParams.autoMergeMethod, - labels: this.createParams.labels ?? [] - }; - } - - public submit = async (): Promise => { - try { - const args: CreatePullRequest = this.copyParams(); - vscode.setState(defaultCreateParams); - await this.postMessage({ - command: 'pr.create', - args, - }); - } catch (e) { - this.updateState({ createError: (typeof e === 'string') ? e : (e.message ? e.message : 'An unknown error occurred.') }); - } - }; - - postMessage = async (message: any): Promise => { - return this._handler?.postMessage(message); - }; - - handleMessage = async (message: { command: string, params?: Partial, scrollPosition?: ScrollPosition }): Promise => { - switch (message.command) { - case 'pr.initialize': - if (!message.params) { - return; - } - if (this.createParams.pendingTitle === undefined) { - message.params.pendingTitle = message.params.defaultTitle; - } - - if (this.createParams.pendingDescription === undefined) { - message.params.pendingDescription = message.params.defaultDescription; - } - - if (this.createParams.baseRemote === undefined) { - message.params.baseRemote = message.params.defaultBaseRemote; - } else if (message.params.baseRemote && ((this.createParams.baseRemote.owner !== message.params.baseRemote.owner) || (this.createParams.baseRemote.repositoryName !== message.params.baseRemote.repositoryName))) { - // Notify the extension of the stored selected remote state - await this.changeBaseRemote( - this.createParams.baseRemote.owner, - this.createParams.baseRemote.repositoryName, - ); - } - - if (this.createParams.baseBranch === undefined) { - message.params.baseBranch = message.params.defaultBaseBranch; - } else if (message.params.baseBranch && (this.createParams.baseBranch !== message.params.baseBranch)) { - // Notify the extension of the stored base branch state - await this.changeBaseBranch(this.createParams.baseBranch); - } - - if (this.createParams.compareRemote === undefined) { - message.params.compareRemote = message.params.defaultCompareRemote; - } else if (message.params.compareRemote && ((this.createParams.compareRemote.owner !== message.params.compareRemote.owner) || (this.createParams.compareRemote.repositoryName !== message.params.compareRemote.repositoryName))) { - // Notify the extension of the stored base branch state - await this.changeCompareRemote( - this.createParams.compareRemote.owner, - this.createParams.compareRemote.repositoryName - ); - } - - if (this.createParams.compareBranch === undefined) { - message.params.compareBranch = message.params.defaultCompareBranch; - } else if (message.params.compareBranch && (this.createParams.compareBranch !== message.params.compareBranch)) { - // Notify the extension of the stored compare branch state - await this.changeCompareBranch(this.createParams.compareBranch); - } - - if (this.createParams.isDraft === undefined) { - message.params.isDraft = message.params.isDraftDefault; - } else { - message.params.isDraft = this.createParams.isDraft; - } - - if (this.createParams.autoMerge === undefined) { - message.params.autoMerge = message.params.autoMergeDefault; - message.params.autoMergeMethod = message.params.defaultMergeMethod; - message.params.isDraft = false; - } else { - message.params.autoMerge = this.createParams.autoMerge; - message.params.autoMergeMethod = this.createParams.autoMergeMethod; - } - - this.updateState(message.params); - return; - - case 'reset': - if (!message.params) { - this.updateState(defaultCreateParams); - return; - } - message.params.pendingTitle = message.params.defaultTitle ?? this.createParams.pendingTitle; - message.params.pendingDescription = message.params.defaultDescription ?? this.createParams.pendingDescription; - message.params.baseRemote = message.params.defaultBaseRemote ?? this.createParams.baseRemote; - message.params.baseBranch = message.params.defaultBaseBranch ?? this.createParams.baseBranch; - message.params.compareBranch = message.params.defaultCompareBranch ?? this.createParams.compareBranch; - message.params.compareRemote = message.params.defaultCompareRemote ?? this.createParams.compareRemote; - message.params.autoMerge = (message.params.autoMergeDefault !== undefined ? message.params.autoMergeDefault : this.createParams.autoMerge); - message.params.autoMergeMethod = (message.params.defaultMergeMethod !== undefined ? message.params.defaultMergeMethod : this.createParams.autoMergeMethod); - if (message.params.autoMergeDefault) { - message.params.isDraft = false; - } - this.updateState(message.params); - return; - - case 'set-scroll': - if (!message.scrollPosition) { - return; - } - window.scrollTo(message.scrollPosition.x, message.scrollPosition.y); - return; - - case 'set-labels': - if (!message.params) { - return; - } - this.updateState(message.params); - return; - } - }; - - public static instance = new CreatePRContext(); -} - -const PullRequestContext = createContext(CreatePRContext.instance); -export default PullRequestContext; diff --git a/webviews/common/createContextNew.ts b/webviews/common/createContextNew.ts index 8d9909b69a..b505c569b2 100644 --- a/webviews/common/createContextNew.ts +++ b/webviews/common/createContextNew.ts @@ -26,7 +26,8 @@ const defaultCreateParams: CreateParamsNew = { pendingDescription: undefined, creating: false, generateTitleAndDescriptionTitle: undefined, - initializeWithGeneratedTitleAndDescription: false + initializeWithGeneratedTitleAndDescription: false, + baseHasMergeQueue: false }; export class CreatePRContextNew { @@ -99,6 +100,7 @@ export class CreatePRContextNew { updateValues.allowAutoMerge = response.allowAutoMerge; updateValues.mergeMethodsAvailability = response.mergeMethodsAvailability; updateValues.autoMergeDefault = response.autoMergeDefault; + updateValues.baseHasMergeQueue = response.baseHasMergeQueue; if (!this.createParams.allowAutoMerge && updateValues.allowAutoMerge) { updateValues.autoMerge = this.createParams.isDraft ? false : updateValues.autoMergeDefault; } diff --git a/webviews/components/automergeSelect.tsx b/webviews/components/automergeSelect.tsx index 00a57eee26..f50ecc69d5 100644 --- a/webviews/components/automergeSelect.tsx +++ b/webviews/components/automergeSelect.tsx @@ -13,7 +13,7 @@ const AutoMergeLabel = ({ busy, baseHasMergeQueue }: { busy: boolean, baseHasMer return ; } else { return ; } }; @@ -41,6 +41,10 @@ export const AutoMerge = ({ const select: React.MutableRefObject = React.useRef() as React.MutableRefObject; const [isBusy, setBusy] = React.useState(false); + const selectedMethod = (): MergeMethod => { + const value: string = select.current?.value ?? 'merge'; + return value as MergeMethod; + }; return (
@@ -53,7 +57,7 @@ export const AutoMerge = ({ disabled={!allowAutoMerge || isDraft || isBusy} onChange={async () => { setBusy(true); - await updateState({ autoMerge: !autoMerge, autoMergeMethod: select.current?.value as MergeMethod }); + await updateState({ autoMerge: !autoMerge, autoMergeMethod: selectedMethod() }); setBusy(false); }} > @@ -67,7 +71,7 @@ export const AutoMerge = ({ mergeMethodsAvailability={mergeMethodsAvailability} onChange={async () => { setBusy(true); - await updateState({ autoMergeMethod: select.current?.value as MergeMethod }); + await updateState({ autoMergeMethod: selectedMethod() }); setBusy(false); }} disabled={isBusy} diff --git a/webviews/createPullRequestViewNew/app.tsx b/webviews/createPullRequestViewNew/app.tsx index f8d8e9d476..12a8003f22 100644 --- a/webviews/createPullRequestViewNew/app.tsx +++ b/webviews/createPullRequestViewNew/app.tsx @@ -41,14 +41,16 @@ export function main() { const ctx = useContext(PullRequestContextNew); const [isBusy, setBusy] = useState(params.creating); const [isGeneratingTitle, setGeneratingTitle] = useState(false); - function createMethodLabel(isDraft?: boolean, autoMerge?: boolean, autoMergeMethod?: MergeMethod): { value: CreateMethod, label: string } { + function createMethodLabel(isDraft?: boolean, autoMerge?: boolean, autoMergeMethod?: MergeMethod, baseHasMergeQueue?: boolean): { value: CreateMethod, label: string } { let value: CreateMethod; let label: string; - if (autoMerge && autoMergeMethod) { + if (autoMerge && baseHasMergeQueue) { + value = 'create-automerge-merge'; + label = 'Merge When Ready'; + } else if (autoMerge && autoMergeMethod) { value = `create-automerge-${autoMergeMethod}` as CreateMethod; const mergeMethodLabel = autoMergeMethod.charAt(0).toUpperCase() + autoMergeMethod.slice(1); label = `Create + Auto-${mergeMethodLabel}`; - } else if (isDraft) { value = 'create-draft'; label = 'Create Draft'; @@ -147,14 +149,18 @@ export function main() { 'github:createPrMenu': true, 'github:createPrMenuDraft': true }; - if (createParams.allowAutoMerge && createParams.mergeMethodsAvailability && createParams.mergeMethodsAvailability['merge']) { - createMenuContexts['github:createPrMenuMerge'] = true; - } - if (createParams.allowAutoMerge && createParams.mergeMethodsAvailability && createParams.mergeMethodsAvailability['squash']) { - createMenuContexts['github:createPrMenuSquash'] = true; - } - if (createParams.allowAutoMerge && createParams.mergeMethodsAvailability && createParams.mergeMethodsAvailability['rebase']) { - createMenuContexts['github:createPrMenuRebase'] = true; + if (createParams.baseHasMergeQueue) { + createMenuContexts['github:createPrMenuMergeWhenReady'] = true; + } else { + if (createParams.allowAutoMerge && createParams.mergeMethodsAvailability && createParams.mergeMethodsAvailability['merge']) { + createMenuContexts['github:createPrMenuMerge'] = true; + } + if (createParams.allowAutoMerge && createParams.mergeMethodsAvailability && createParams.mergeMethodsAvailability['squash']) { + createMenuContexts['github:createPrMenuSquash'] = true; + } + if (createParams.allowAutoMerge && createParams.mergeMethodsAvailability && createParams.mergeMethodsAvailability['rebase']) { + createMenuContexts['github:createPrMenuRebase'] = true; + } } const stringified = JSON.stringify(createMenuContexts); return stringified; @@ -333,8 +339,8 @@ export function main() { makeCreateMenuContext(params)} defaultAction={onCreateButton} - defaultOptionLabel={() => createMethodLabel(ctx.createParams.isDraft, ctx.createParams.autoMerge, ctx.createParams.autoMergeMethod).label} - defaultOptionValue={() => createMethodLabel(ctx.createParams.isDraft, ctx.createParams.autoMerge, ctx.createParams.autoMergeMethod).value} + 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 || !isCreateable || !ctx.initialized} />