From 9b207d04964e14033a59fab82d32fd8beaaa8fef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:43:46 +0000 Subject: [PATCH 01/12] Initial plan From 58cfdcda224dd502643b69fb70b465817e1b115e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:51:26 +0000 Subject: [PATCH 02/12] Initial plan for base branch editing feature Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/@types/vscode.proposed.chatParticipantAdditions.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index 71520fa1ec..aa7001a3d2 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -105,6 +105,7 @@ declare module 'vscode' { isComplete?: boolean; toolSpecificData?: ChatTerminalToolInvocationData; fromSubAgent?: boolean; + presentation?: 'hidden' | 'hiddenAfterComplete' | undefined; constructor(toolName: string, toolCallId: string, isError?: boolean); } From 17e05aba34b0732003af895572ab0010b565341f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:55:12 +0000 Subject: [PATCH 03/12] Add base branch editing functionality Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/github/pullRequestModel.ts | 31 +++++++++++++++++ src/github/pullRequestOverview.ts | 55 +++++++++++++++++++++++++++++++ webviews/common/context.tsx | 1 + webviews/components/header.tsx | 14 ++++++-- 4 files changed, 98 insertions(+), 3 deletions(-) diff --git a/src/github/pullRequestModel.ts b/src/github/pullRequestModel.ts index 4cb54d86ed..d75015132b 100644 --- a/src/github/pullRequestModel.ts +++ b/src/github/pullRequestModel.ts @@ -1199,6 +1199,37 @@ export class PullRequestModel extends IssueModel implements IPullRe return true; } + /** + * Update the base branch of the pull request. + * @param newBaseBranch The new base branch name + */ + async updateBaseBranch(newBaseBranch: string): Promise { + Logger.debug(`Updating base branch to ${newBaseBranch} - enter`, PullRequestModel.ID); + try { + const { mutate, schema } = await this.githubRepository.ensure(); + + const { data } = await mutate({ + mutation: schema.UpdatePullRequest, + variables: { + input: { + pullRequestId: this.graphNodeId, + baseRefName: newBaseBranch, + }, + }, + }); + + if (data?.updateIssue?.issue) { + // Update the local base branch reference + this.base.name = newBaseBranch; + this._onDidChange.fire({ base: true }); + } + Logger.debug(`Updating base branch to ${newBaseBranch} - done`, PullRequestModel.ID); + } catch (e) { + Logger.error(`Updating base branch to ${newBaseBranch} failed: ${e}`, PullRequestModel.ID); + throw e; + } + } + /** * Get existing requests to review. */ diff --git a/src/github/pullRequestOverview.ts b/src/github/pullRequestOverview.ts index 50aed1e41f..f3f0e6cee5 100644 --- a/src/github/pullRequestOverview.ts +++ b/src/github/pullRequestOverview.ts @@ -425,6 +425,8 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel): Promise { + const quickPick = vscode.window.createQuickPick(); + + try { + quickPick.busy = true; + quickPick.canSelectMany = false; + quickPick.placeholder = 'Select a new base branch'; + quickPick.show(); + + // List branches from the repository + const branches = await this._item.githubRepository.listBranches( + this._item.remote.owner, + this._item.remote.repositoryName + ); + + quickPick.items = branches + .filter(branch => branch !== this._item.base.name) + .map(branch => ({ + label: branch, + branch: branch + })); + + quickPick.busy = false; + const acceptPromise = asPromise(quickPick.onDidAccept).then(() => { + return quickPick.selectedItems[0]?.branch; + }); + const hidePromise = asPromise(quickPick.onDidHide); + const selectedBranch = await Promise.race([acceptPromise, hidePromise]); + quickPick.busy = true; + quickPick.enabled = false; + + if (selectedBranch) { + try { + // Update the base branch using GraphQL mutation + await this._item.updateBaseBranch(selectedBranch); + // Refresh the panel to reflect the changes + await this.refreshPanel(); + await this._replyMessage(message, {}); + } catch (e) { + Logger.error(formatError(e), PullRequestOverviewPanel.ID); + vscode.window.showErrorMessage(vscode.l10n.t('Changing base branch failed. {0}', formatError(e))); + this._throwError(message, `${formatError(e)}`); + } + } + } catch (e) { + Logger.error(formatError(e), PullRequestOverviewPanel.ID); + vscode.window.showErrorMessage(formatError(e)); + } finally { + quickPick.hide(); + quickPick.dispose(); + } + } + override dispose() { super.dispose(); disposeAll(this._prListeners); diff --git a/webviews/common/context.tsx b/webviews/common/context.tsx index d5aa38643a..c2560c267d 100644 --- a/webviews/common/context.tsx +++ b/webviews/common/context.tsx @@ -92,6 +92,7 @@ export class PRContext { public readyForReviewAndMerge = (args: { mergeMethod: MergeMethod }): Promise => this.postMessage({ command: 'pr.readyForReviewAndMerge', args }); public addReviewers = () => this.postMessage({ command: 'pr.change-reviewers' }); + public changeBaseBranch = () => this.postMessage({ command: 'pr.change-base-branch' }); public changeProjects = (): Promise => this.postMessage({ command: 'pr.change-projects' }); public removeProject = (project: IProjectItem) => this.postMessage({ command: 'pr.remove-project', args: project }); public addMilestone = () => this.postMessage({ command: 'pr.add-milestone' }); diff --git a/webviews/components/header.tsx b/webviews/components/header.tsx index 9418515e19..f8dfce79cf 100644 --- a/webviews/components/header.tsx +++ b/webviews/components/header.tsx @@ -52,7 +52,7 @@ export function Header({ owner={owner} repo={repo} /> - +
{!isIssue ? (<> {getActionText(state)} into{' '} - {base} from {head} + {base} + {canEdit && state === GithubItemStateEnum.Open ? ( + + ) : null} + {' '}from {head} ) : null}
From 4f4fd0ead999084acc715dd455ba507ca4b679e2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 11:00:37 +0000 Subject: [PATCH 04/12] Fix TypeScript errors in base branch editing feature Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/github/issueModel.ts | 1 + src/github/pullRequestModel.ts | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/github/issueModel.ts b/src/github/issueModel.ts index d64ab70a19..23750c43f4 100644 --- a/src/github/issueModel.ts +++ b/src/github/issueModel.ts @@ -41,6 +41,7 @@ export interface IssueChangeEvent { draft?: true; reviewers?: true; + base?: true; } export class IssueModel extends Disposable { diff --git a/src/github/pullRequestModel.ts b/src/github/pullRequestModel.ts index d75015132b..3537e3d78d 100644 --- a/src/github/pullRequestModel.ts +++ b/src/github/pullRequestModel.ts @@ -41,6 +41,7 @@ import { SubmitReviewResponse, TimelineEventsResponse, UnresolveReviewThreadResponse, + UpdateIssueResponse, } from './graphql'; import { AccountType, @@ -1208,7 +1209,7 @@ export class PullRequestModel extends IssueModel implements IPullRe try { const { mutate, schema } = await this.githubRepository.ensure(); - const { data } = await mutate({ + const { data } = await mutate({ mutation: schema.UpdatePullRequest, variables: { input: { @@ -1220,7 +1221,7 @@ export class PullRequestModel extends IssueModel implements IPullRe if (data?.updateIssue?.issue) { // Update the local base branch reference - this.base.name = newBaseBranch; + this.base.ref = newBaseBranch; this._onDidChange.fire({ base: true }); } Logger.debug(`Updating base branch to ${newBaseBranch} - done`, PullRequestModel.ID); From dd547f862a0d5bd81b81afd37e0d0df4e38e2d47 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 11:07:29 +0000 Subject: [PATCH 05/12] Address code review feedback - use immutable GitHubRef update Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/github/pullRequestModel.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/github/pullRequestModel.ts b/src/github/pullRequestModel.ts index 3537e3d78d..3a9c7a1021 100644 --- a/src/github/pullRequestModel.ts +++ b/src/github/pullRequestModel.ts @@ -1220,8 +1220,17 @@ export class PullRequestModel extends IssueModel implements IPullRe }); if (data?.updateIssue?.issue) { - // Update the local base branch reference - this.base.ref = newBaseBranch; + // Update the local base branch reference by creating a new GitHubRef instance + const cloneUrl = this.base.repositoryCloneUrl.toString() || ''; + this.base = new GitHubRef( + newBaseBranch, + `${this.base.owner}:${newBaseBranch}`, + this.base.sha, + cloneUrl, + this.base.owner, + this.base.name, + this.base.isInOrganization + ); this._onDidChange.fire({ base: true }); } Logger.debug(`Updating base branch to ${newBaseBranch} - done`, PullRequestModel.ID); From b8c606267e53be5036a969470bf7a5cb9bbabdbf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 11:12:59 +0000 Subject: [PATCH 06/12] Add test for updateBaseBranch method Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/test/github/pullRequestModel.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/github/pullRequestModel.test.ts b/src/test/github/pullRequestModel.test.ts index 5497cde4ab..f7fa80af71 100644 --- a/src/test/github/pullRequestModel.test.ts +++ b/src/test/github/pullRequestModel.test.ts @@ -148,4 +148,14 @@ describe('PullRequestModel', function () { assert.strictEqual(onDidChangeReviewThreads.getCall(0).args[0]['removed'].length, 0); }); }); + + describe('updateBaseBranch', function () { + it('should have updateBaseBranch method', function () { + const pr = new PullRequestBuilder().build(); + const model = new PullRequestModel(credentials, telemetry, repo, remote, convertRESTPullRequestToRawPullRequest(pr, repo)); + + // Verify the method exists + assert.strictEqual(typeof model.updateBaseBranch, 'function'); + }); + }); }); From 0a485e65fd798c20527a8d87026ffbb0c7ff1be4 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Fri, 12 Dec 2025 10:23:47 +0100 Subject: [PATCH 07/12] Reuse branch picks code --- src/github/createPRViewProvider.ts | 42 +++--------------------------- src/github/pullRequestOverview.ts | 20 +++----------- src/github/quickPicks.ts | 38 +++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 55 deletions(-) diff --git a/src/github/createPRViewProvider.ts b/src/github/createPRViewProvider.ts index 40763443bb..60b1bc7ace 100644 --- a/src/github/createPRViewProvider.ts +++ b/src/github/createPRViewProvider.ts @@ -15,12 +15,12 @@ import { IAccount, ILabel, IMilestone, IProject, isITeam, ITeam, MergeMethod, Re import { BaseBranchMetadata, PullRequestGitHelper } from './pullRequestGitHelper'; import { PullRequestModel } from './pullRequestModel'; import { getDefaultMergeMethod } from './pullRequestOverview'; -import { getAssigneesQuickPickItems, getLabelOptions, getMilestoneFromQuickPick, getProjectFromQuickPick, reviewersQuickPick } from './quickPicks'; +import { branchPicks, getAssigneesQuickPickItems, getLabelOptions, getMilestoneFromQuickPick, getProjectFromQuickPick, reviewersQuickPick } from './quickPicks'; import { getIssueNumberLabelFromParsed, ISSUE_EXPRESSION, ISSUE_OR_URL_EXPRESSION, parseIssueExpressionOutput, variableSubstitution } from './utils'; import { DisplayLabel, PreReviewState } from './views'; import { RemoteInfo } from '../../common/types'; import { ChooseBaseRemoteAndBranchResult, ChooseCompareRemoteAndBranchResult, ChooseRemoteAndBranchArgs, CreateParamsNew, CreatePullRequestNew, TitleAndDescriptionArgs } from '../../common/views'; -import type { Branch, Ref } from '../api/api'; +import type { Branch } from '../api/api'; import { GitHubServerType } from '../common/authentication'; import { emojify, ensureEmojis } from '../common/emoji'; import { commands, contexts } from '../common/executeCommands'; @@ -812,40 +812,6 @@ export class CreatePullRequestViewProvider extends BaseCreatePullRequestViewProv }); } - private async branchPicks(githubRepository: GitHubRepository, changeRepoMessage: string, isBase: boolean): Promise<(vscode.QuickPickItem & { remote?: RemoteInfo, branch?: string })[]> { - let branches: (string | Ref)[]; - if (isBase) { - // For the base, we only want to show branches from GitHub. - branches = await githubRepository.listBranches(githubRepository.remote.owner, githubRepository.remote.repositoryName); - } else { - // For the compare, we only want to show local branches. - branches = (await this._folderRepositoryManager.repository.getBranches({ remote: false })).filter(branch => branch.name); - } - // TODO: @alexr00 - Add sorting so that the most likely to be used branch (ex main or release if base) is at the top of the list. - const branchPicks: (vscode.QuickPickItem & { remote?: RemoteInfo, branch?: string })[] = branches.map(branch => { - const branchName = typeof branch === 'string' ? branch : branch.name!; - const pick: (vscode.QuickPickItem & { remote: RemoteInfo, branch: string }) = { - iconPath: new vscode.ThemeIcon('git-branch'), - label: branchName, - remote: { - owner: githubRepository.remote.owner, - repositoryName: githubRepository.remote.repositoryName - }, - branch: branchName - }; - return pick; - }); - branchPicks.unshift({ - kind: vscode.QuickPickItemKind.Separator, - label: `${githubRepository.remote.owner}/${githubRepository.remote.repositoryName}` - }); - branchPicks.unshift({ - iconPath: new vscode.ThemeIcon('repo'), - label: changeRepoMessage - }); - return branchPicks; - } - private async processRemoteAndBranchResult(githubRepository: GitHubRepository, result: { remote: RemoteInfo, branch: string }, isBase: boolean) { const [defaultBranch, viewerPermission] = await Promise.all([githubRepository.getDefaultBranch(), githubRepository.getViewerPermission()]); @@ -922,7 +888,7 @@ export class CreatePullRequestViewProvider extends BaseCreatePullRequestViewProv quickPick.placeholder = githubRepository ? branchPlaceholder : remotePlaceholder; quickPick.show(); quickPick.busy = true; - quickPick.items = githubRepository ? await this.branchPicks(githubRepository, chooseDifferentRemote, isBase) : await this.remotePicks(isBase); + quickPick.items = githubRepository ? await branchPicks(githubRepository, this._folderRepositoryManager, chooseDifferentRemote, isBase) : await this.remotePicks(isBase); const activeItem = message.args.currentBranch ? quickPick.items.find(item => item.branch === message.args.currentBranch) : undefined; quickPick.activeItems = activeItem ? [activeItem] : []; quickPick.busy = false; @@ -941,7 +907,7 @@ export class CreatePullRequestViewProvider extends BaseCreatePullRequestViewProv const selectedRemote = selectedPick as vscode.QuickPickItem & { remote: RemoteInfo }; quickPick.busy = true; githubRepository = this._folderRepositoryManager.findRepo(repo => repo.remote.owner === selectedRemote.remote.owner && repo.remote.repositoryName === selectedRemote.remote.repositoryName)!; - quickPick.items = await this.branchPicks(githubRepository, chooseDifferentRemote, isBase); + quickPick.items = await branchPicks(githubRepository, this._folderRepositoryManager, chooseDifferentRemote, isBase); quickPick.placeholder = branchPlaceholder; quickPick.busy = false; } else if (selectedPick.branch && selectedPick.remote) { diff --git a/src/github/pullRequestOverview.ts b/src/github/pullRequestOverview.ts index f3f0e6cee5..7d9ac5aa99 100644 --- a/src/github/pullRequestOverview.ts +++ b/src/github/pullRequestOverview.ts @@ -23,7 +23,7 @@ import { import { IssueOverviewPanel } from './issueOverview'; import { isCopilotOnMyBehalf, PullRequestModel } from './pullRequestModel'; import { PullRequestReviewCommon, ReviewContext } from './pullRequestReviewCommon'; -import { pickEmail, reviewersQuickPick } from './quickPicks'; +import { branchPicks, pickEmail, reviewersQuickPick } from './quickPicks'; import { parseReviewers } from './utils'; import { CancelCodingAgentReply, DeleteReviewResult, MergeArguments, MergeResult, PullRequest, ReviewType } from './views'; import { IComment } from '../common/comment'; @@ -813,21 +813,10 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel branch !== this._item.base.name) - .map(branch => ({ - label: branch, - branch: branch - })); + quickPick.items = await branchPicks(this._item.githubRepository, this._folderRepositoryManager, undefined, true); quickPick.busy = false; const acceptPromise = asPromise(quickPick.onDidAccept).then(() => { @@ -840,10 +829,7 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel { + let branches: (string | Ref)[]; + if (isBase) { + // For the base, we only want to show branches from GitHub. + branches = await githubRepository.listBranches(githubRepository.remote.owner, githubRepository.remote.repositoryName); + } else { + // For the compare, we only want to show local branches. + branches = (await folderRepoManager.repository.getBranches({ remote: false })).filter(branch => branch.name); + } + // TODO: @alexr00 - Add sorting so that the most likely to be used branch (ex main or release if base) is at the top of the list. + const branchPicks: (vscode.QuickPickItem & { remote?: RemoteInfo, branch?: string })[] = branches.map(branch => { + const branchName = typeof branch === 'string' ? branch : branch.name!; + const pick: (vscode.QuickPickItem & { remote: RemoteInfo, branch: string }) = { + iconPath: new vscode.ThemeIcon('git-branch'), + label: branchName, + remote: { + owner: githubRepository.remote.owner, + repositoryName: githubRepository.remote.repositoryName + }, + branch: branchName + }; + return pick; + }); + branchPicks.unshift({ + kind: vscode.QuickPickItemKind.Separator, + label: `${githubRepository.remote.owner}/${githubRepository.remote.repositoryName}` + }); + if (changeRepoMessage) { + branchPicks.unshift({ + iconPath: new vscode.ThemeIcon('repo'), + label: changeRepoMessage + }); + } + return branchPicks; } \ No newline at end of file From 9465868b784244a355d5f49637c205ac35bbc20a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Dec 2025 09:39:48 +0000 Subject: [PATCH 08/12] Add 4px top margin to edit button and include base branch in reply Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/github/pullRequestOverview.ts | 2 +- webviews/common/context.tsx | 7 ++++++- webviews/editorWebview/index.css | 4 ++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/github/pullRequestOverview.ts b/src/github/pullRequestOverview.ts index 7d9ac5aa99..5c5d708176 100644 --- a/src/github/pullRequestOverview.ts +++ b/src/github/pullRequestOverview.ts @@ -830,7 +830,7 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel => this.postMessage({ command: 'pr.readyForReviewAndMerge', args }); public addReviewers = () => this.postMessage({ command: 'pr.change-reviewers' }); - public changeBaseBranch = () => this.postMessage({ command: 'pr.change-base-branch' }); + public changeBaseBranch = async () => { + const result = await this.postMessage({ command: 'pr.change-base-branch' }); + if (result?.base) { + this.updatePR({ base: result.base }); + } + }; public changeProjects = (): Promise => this.postMessage({ command: 'pr.change-projects' }); public removeProject = (project: IProjectItem) => this.postMessage({ command: 'pr.remove-project', args: project }); public addMilestone = () => this.postMessage({ command: 'pr.add-milestone' }); diff --git a/webviews/editorWebview/index.css b/webviews/editorWebview/index.css index 4716e0152e..90de5aaf7c 100644 --- a/webviews/editorWebview/index.css +++ b/webviews/editorWebview/index.css @@ -541,6 +541,10 @@ body button .icon { border-radius: 4px; } +.merge-branches .icon-button { + margin-top: 4px; +} + .subtitle .created-at { margin-left: auto; white-space: nowrap; From d7b7d3bbc0feac8372deebac270332f464ab80db Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Fri, 12 Dec 2025 11:29:22 +0100 Subject: [PATCH 09/12] Use a type --- src/github/pullRequestOverview.ts | 7 +++++-- src/github/views.ts | 4 ++++ webviews/common/context.tsx | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/github/pullRequestOverview.ts b/src/github/pullRequestOverview.ts index 5c5d708176..825724f513 100644 --- a/src/github/pullRequestOverview.ts +++ b/src/github/pullRequestOverview.ts @@ -25,7 +25,7 @@ import { isCopilotOnMyBehalf, PullRequestModel } from './pullRequestModel'; import { PullRequestReviewCommon, ReviewContext } from './pullRequestReviewCommon'; import { branchPicks, pickEmail, reviewersQuickPick } from './quickPicks'; import { parseReviewers } from './utils'; -import { CancelCodingAgentReply, DeleteReviewResult, MergeArguments, MergeResult, PullRequest, ReviewType } from './views'; +import { CancelCodingAgentReply, ChangeBaseReply, DeleteReviewResult, MergeArguments, MergeResult, PullRequest, ReviewType } from './views'; import { IComment } from '../common/comment'; import { COPILOT_SWE_AGENT, copilotEventToStatus, CopilotPRStatus, mostRecentCopilotEvent } from '../common/copilot'; import { commands, contexts } from '../common/executeCommands'; @@ -830,7 +830,10 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel this.postMessage({ command: 'pr.change-reviewers' }); public changeBaseBranch = async () => { - const result = await this.postMessage({ command: 'pr.change-base-branch' }); + const result: ChangeBaseReply = await this.postMessage({ command: 'pr.change-base-branch' }); if (result?.base) { this.updatePR({ base: result.base }); } From 0633130b35213b1b42b149891a6eeccff3df02c5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Dec 2025 10:50:35 +0000 Subject: [PATCH 10/12] Add BaseRefChanged timeline event and include in ChangeBaseReply Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/common/timelineEvent.ts | 12 +++++++++++- src/github/graphql.ts | 13 +++++++++++-- src/github/pullRequestOverview.ts | 4 +++- src/github/queriesShared.gql | 13 +++++++++++++ src/github/utils.ts | 15 +++++++++++++++ src/github/views.ts | 1 + webviews/common/context.tsx | 2 +- webviews/components/timeline.tsx | 21 +++++++++++++++++++++ 8 files changed, 76 insertions(+), 5 deletions(-) diff --git a/src/common/timelineEvent.ts b/src/common/timelineEvent.ts index 8ef0c07ee0..7fa1cf656e 100644 --- a/src/common/timelineEvent.ts +++ b/src/common/timelineEvent.ts @@ -22,6 +22,7 @@ export enum EventType { CrossReferenced, Closed, Reopened, + BaseRefChanged, CopilotStarted, CopilotFinished, CopilotFinishedError, @@ -156,6 +157,15 @@ export interface ReopenedEvent { createdAt: string; } +export interface BaseRefChangedEvent { + id: string; + event: EventType.BaseRefChanged; + actor: IActor; + createdAt: string; + currentRefName: string; + previousRefName: string; +} + export interface SessionPullInfo { id: number; host: string; @@ -192,4 +202,4 @@ export interface CopilotFinishedErrorEvent { sessionLink: SessionLinkInfo; } -export type TimelineEvent = CommitEvent | ReviewEvent | CommentEvent | NewCommitsSinceReviewEvent | MergedEvent | AssignEvent | UnassignEvent | HeadRefDeleteEvent | CrossReferencedEvent | ClosedEvent | ReopenedEvent | CopilotStartedEvent | CopilotFinishedEvent | CopilotFinishedErrorEvent; +export type TimelineEvent = CommitEvent | ReviewEvent | CommentEvent | NewCommitsSinceReviewEvent | MergedEvent | AssignEvent | UnassignEvent | HeadRefDeleteEvent | CrossReferencedEvent | ClosedEvent | ReopenedEvent | BaseRefChangedEvent | CopilotStartedEvent | CopilotFinishedEvent | CopilotFinishedErrorEvent; diff --git a/src/github/graphql.ts b/src/github/graphql.ts index 68f41b1ded..00838018f7 100644 --- a/src/github/graphql.ts +++ b/src/github/graphql.ts @@ -68,6 +68,15 @@ export interface ReopenedEvent { createdAt: string; } +export interface BaseRefChangedEvent { + __typename: string; + id: string; + actor: Actor; + createdAt: string; + currentRefName: string; + previousRefName: string; +} + export interface AbbreviatedIssueComment { author: Account; body: string; @@ -265,7 +274,7 @@ export interface TimelineEventsResponse { repository: { pullRequest: { timelineItems: { - nodes: (MergedEvent | Review | IssueComment | Commit | AssignedEvent | HeadRefDeletedEvent | null)[]; + nodes: (MergedEvent | Review | IssueComment | Commit | AssignedEvent | HeadRefDeletedEvent | BaseRefChangedEvent | null)[]; }; }; } | null; @@ -1051,7 +1060,7 @@ export interface MergePullRequestResponse { mergePullRequest: { pullRequest: PullRequest & { timelineItems: { - nodes: (MergedEvent | Review | IssueComment | Commit | AssignedEvent | HeadRefDeletedEvent)[] + nodes: (MergedEvent | Review | IssueComment | Commit | AssignedEvent | HeadRefDeletedEvent | BaseRefChangedEvent)[] } }; } diff --git a/src/github/pullRequestOverview.ts b/src/github/pullRequestOverview.ts index 825724f513..ce9df400b2 100644 --- a/src/github/pullRequestOverview.ts +++ b/src/github/pullRequestOverview.ts @@ -830,8 +830,10 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel { const result: ChangeBaseReply = await this.postMessage({ command: 'pr.change-base-branch' }); if (result?.base) { - this.updatePR({ base: result.base }); + this.updatePR({ base: result.base, events: result.events }); } }; public changeProjects = (): Promise => this.postMessage({ command: 'pr.change-projects' }); diff --git a/webviews/components/timeline.tsx b/webviews/components/timeline.tsx index b3b1cd24a0..3480bca712 100644 --- a/webviews/components/timeline.tsx +++ b/webviews/components/timeline.tsx @@ -13,6 +13,7 @@ import { AuthorLink, Avatar } from './user'; import { IComment } from '../../src/common/comment'; import { AssignEvent, + BaseRefChangedEvent, ClosedEvent, CommentEvent, CommitEvent, @@ -89,6 +90,8 @@ export const Timeline = ({ events, isIssue }: { events: TimelineEvent[], isIssue return ; case EventType.Reopened: return ; + case EventType.BaseRefChanged: + return ; case EventType.NewCommitsSinceReview: return ; case EventType.CopilotStarted: @@ -516,6 +519,24 @@ const ReopenedEventView = ({ event, isIssue }: { event: ReopenedEvent, isIssue: ); }; +const BaseRefChangedEventView = ({ event }: { event: BaseRefChangedEvent }) => { + const { actor, createdAt, currentRefName, previousRefName } = event; + return ( +
+
+
+ +
+ +
+ changed the base branch from {previousRefName} to {currentRefName} +
+
+ +
+ ); +}; + const CopilotStartedEventView = (event: CopilotStartedEvent) => { const { createdAt, onBehalfOf, sessionLink } = event; const { openSessionLog } = useContext(PullRequestContext); From e8ecac6cc6eaa77dd775c02f361f5102b96fde29 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:54:40 +0000 Subject: [PATCH 11/12] Make base branch clickable instead of using edit icon button Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- .../vscode.proposed.chatSessionsProvider.d.ts | 5 +++++ webviews/components/header.tsx | 14 ++++++++++---- webviews/editorWebview/index.css | 17 +++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/@types/vscode.proposed.chatSessionsProvider.d.ts b/src/@types/vscode.proposed.chatSessionsProvider.d.ts index bd4e624430..772fc387b9 100644 --- a/src/@types/vscode.proposed.chatSessionsProvider.d.ts +++ b/src/@types/vscode.proposed.chatSessionsProvider.d.ts @@ -95,6 +95,11 @@ declare module 'vscode' { */ description?: string | MarkdownString; + /** + * An optional badge that provides additional context about the chat session. + */ + badge?: string | MarkdownString; + /** * An optional status indicating the current state of the session. */ diff --git a/webviews/components/header.tsx b/webviews/components/header.tsx index f8dfce79cf..026f445996 100644 --- a/webviews/components/header.tsx +++ b/webviews/components/header.tsx @@ -275,12 +275,18 @@ function Subtitle({ state, stateReason, isDraft, isIssue, author, base, head, co
{!isIssue ? (<> {getActionText(state)} into{' '} - {base} {canEdit && state === GithubItemStateEnum.Open ? ( - - ) : null} + ) : ( + {base} + )} {' '}from {head} ) : null}
diff --git a/webviews/editorWebview/index.css b/webviews/editorWebview/index.css index 90de5aaf7c..0ffdcca3fd 100644 --- a/webviews/editorWebview/index.css +++ b/webviews/editorWebview/index.css @@ -541,6 +541,23 @@ body button .icon { border-radius: 4px; } +.branch-tag-button { + border: 1px solid transparent; + cursor: pointer; + font-family: inherit; + font-size: inherit; +} + +.branch-tag-button:hover { + background: var(--vscode-button-hoverBackground); + color: var(--vscode-button-foreground); + border-color: var(--vscode-button-border); +} + +.branch-tag-button:active { + background: var(--vscode-button-background); +} + .merge-branches .icon-button { margin-top: 4px; } From 4e67ecac91eb7ecad252f29259ab706823928832 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 10:39:20 +0000 Subject: [PATCH 12/12] Use code styling for base branch text and secondary button styling Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- webviews/components/header.tsx | 4 ++-- webviews/editorWebview/index.css | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/webviews/components/header.tsx b/webviews/components/header.tsx index 026f445996..80acf14b23 100644 --- a/webviews/components/header.tsx +++ b/webviews/components/header.tsx @@ -279,10 +279,10 @@ function Subtitle({ state, stateReason, isDraft, isIssue, author, base, head, co ) : ( {base} diff --git a/webviews/editorWebview/index.css b/webviews/editorWebview/index.css index 0ffdcca3fd..951d135afb 100644 --- a/webviews/editorWebview/index.css +++ b/webviews/editorWebview/index.css @@ -542,20 +542,28 @@ body button .icon { } .branch-tag-button { + background-color: var(--vscode-button-secondaryBackground); + color: var(--vscode-button-secondaryForeground); border: 1px solid transparent; + border-radius: 4px; cursor: pointer; + padding: 0; + margin-top: 3px; font-family: inherit; font-size: inherit; } -.branch-tag-button:hover { - background: var(--vscode-button-hoverBackground); - color: var(--vscode-button-foreground); - border-color: var(--vscode-button-border); +.branch-tag-button:hover:enabled { + background-color: var(--vscode-button-secondaryHoverBackground); } .branch-tag-button:active { - background: var(--vscode-button-background); + background-color: var(--vscode-button-secondaryHoverBackground); +} + +.branch-tag-button .branch-tag { + background: transparent; + margin-top: 0; } .merge-branches .icon-button {