Skip to content

Commit 17e05ab

Browse files
Copilotalexr00
andcommitted
Add base branch editing functionality
Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com>
1 parent 58cfdcd commit 17e05ab

File tree

4 files changed

+98
-3
lines changed

4 files changed

+98
-3
lines changed

src/github/pullRequestModel.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,6 +1199,37 @@ export class PullRequestModel extends IssueModel<PullRequest> implements IPullRe
11991199
return true;
12001200
}
12011201

1202+
/**
1203+
* Update the base branch of the pull request.
1204+
* @param newBaseBranch The new base branch name
1205+
*/
1206+
async updateBaseBranch(newBaseBranch: string): Promise<void> {
1207+
Logger.debug(`Updating base branch to ${newBaseBranch} - enter`, PullRequestModel.ID);
1208+
try {
1209+
const { mutate, schema } = await this.githubRepository.ensure();
1210+
1211+
const { data } = await mutate({
1212+
mutation: schema.UpdatePullRequest,
1213+
variables: {
1214+
input: {
1215+
pullRequestId: this.graphNodeId,
1216+
baseRefName: newBaseBranch,
1217+
},
1218+
},
1219+
});
1220+
1221+
if (data?.updateIssue?.issue) {
1222+
// Update the local base branch reference
1223+
this.base.name = newBaseBranch;
1224+
this._onDidChange.fire({ base: true });
1225+
}
1226+
Logger.debug(`Updating base branch to ${newBaseBranch} - done`, PullRequestModel.ID);
1227+
} catch (e) {
1228+
Logger.error(`Updating base branch to ${newBaseBranch} failed: ${e}`, PullRequestModel.ID);
1229+
throw e;
1230+
}
1231+
}
1232+
12021233
/**
12031234
* Get existing requests to review.
12041235
*/

src/github/pullRequestOverview.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,8 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
425425
return this.openCommitChanges(message);
426426
case 'pr.delete-review':
427427
return this.deleteReview(message);
428+
case 'pr.change-base-branch':
429+
return this.changeBaseBranch(message);
428430
}
429431
}
430432

@@ -805,6 +807,59 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
805807
}
806808
}
807809

810+
private async changeBaseBranch(message: IRequestMessage<void>): Promise<void> {
811+
const quickPick = vscode.window.createQuickPick<vscode.QuickPickItem & { branch?: string }>();
812+
813+
try {
814+
quickPick.busy = true;
815+
quickPick.canSelectMany = false;
816+
quickPick.placeholder = 'Select a new base branch';
817+
quickPick.show();
818+
819+
// List branches from the repository
820+
const branches = await this._item.githubRepository.listBranches(
821+
this._item.remote.owner,
822+
this._item.remote.repositoryName
823+
);
824+
825+
quickPick.items = branches
826+
.filter(branch => branch !== this._item.base.name)
827+
.map(branch => ({
828+
label: branch,
829+
branch: branch
830+
}));
831+
832+
quickPick.busy = false;
833+
const acceptPromise = asPromise<void>(quickPick.onDidAccept).then(() => {
834+
return quickPick.selectedItems[0]?.branch;
835+
});
836+
const hidePromise = asPromise<void>(quickPick.onDidHide);
837+
const selectedBranch = await Promise.race<string | void>([acceptPromise, hidePromise]);
838+
quickPick.busy = true;
839+
quickPick.enabled = false;
840+
841+
if (selectedBranch) {
842+
try {
843+
// Update the base branch using GraphQL mutation
844+
await this._item.updateBaseBranch(selectedBranch);
845+
// Refresh the panel to reflect the changes
846+
await this.refreshPanel();
847+
await this._replyMessage(message, {});
848+
} catch (e) {
849+
Logger.error(formatError(e), PullRequestOverviewPanel.ID);
850+
vscode.window.showErrorMessage(vscode.l10n.t('Changing base branch failed. {0}', formatError(e)));
851+
this._throwError(message, `${formatError(e)}`);
852+
}
853+
}
854+
} catch (e) {
855+
Logger.error(formatError(e), PullRequestOverviewPanel.ID);
856+
vscode.window.showErrorMessage(formatError(e));
857+
} finally {
858+
quickPick.hide();
859+
quickPick.dispose();
860+
}
861+
}
862+
808863
override dispose() {
809864
super.dispose();
810865
disposeAll(this._prListeners);

webviews/common/context.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export class PRContext {
9292
public readyForReviewAndMerge = (args: { mergeMethod: MergeMethod }): Promise<ReadyForReview> => this.postMessage({ command: 'pr.readyForReviewAndMerge', args });
9393

9494
public addReviewers = () => this.postMessage({ command: 'pr.change-reviewers' });
95+
public changeBaseBranch = () => this.postMessage({ command: 'pr.change-base-branch' });
9596
public changeProjects = (): Promise<ProjectItemsReply> => this.postMessage({ command: 'pr.change-projects' });
9697
public removeProject = (project: IProjectItem) => this.postMessage({ command: 'pr.remove-project', args: project });
9798
public addMilestone = () => this.postMessage({ command: 'pr.add-milestone' });

webviews/components/header.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export function Header({
5252
owner={owner}
5353
repo={repo}
5454
/>
55-
<Subtitle state={state} stateReason={stateReason} head={head} base={base} author={author} isIssue={isIssue} isDraft={isDraft} codingAgentEvent={codingAgentEvent} />
55+
<Subtitle state={state} stateReason={stateReason} head={head} base={base} author={author} isIssue={isIssue} isDraft={isDraft} codingAgentEvent={codingAgentEvent} canEdit={canEdit} />
5656
<div className="header-actions">
5757
<ButtonGroup
5858
isCurrentlyCheckedOut={isCurrentlyCheckedOut}
@@ -248,9 +248,11 @@ interface SubtitleProps {
248248
base: string;
249249
head: string;
250250
codingAgentEvent: TimelineEvent | undefined;
251+
canEdit: boolean;
251252
}
252253

253-
function Subtitle({ state, stateReason, isDraft, isIssue, author, base, head, codingAgentEvent }: SubtitleProps): JSX.Element {
254+
function Subtitle({ state, stateReason, isDraft, isIssue, author, base, head, codingAgentEvent, canEdit }: SubtitleProps): JSX.Element {
255+
const { changeBaseBranch } = useContext(PullRequestContext);
254256
const { text, color, icon } = getStatus(state, !!isDraft, isIssue, stateReason);
255257
const copilotStatus = copilotEventToStatus(codingAgentEvent);
256258
let copilotStatusIcon: JSX.Element | undefined;
@@ -273,7 +275,13 @@ function Subtitle({ state, stateReason, isDraft, isIssue, author, base, head, co
273275
<div className="merge-branches">
274276
<AuthorLink for={author} /> {!isIssue ? (<>
275277
{getActionText(state)} into{' '}
276-
<code className="branch-tag">{base}</code> from <code className="branch-tag">{head}</code>
278+
<code className="branch-tag">{base}</code>
279+
{canEdit && state === GithubItemStateEnum.Open ? (
280+
<button title="Change base branch" onClick={changeBaseBranch} className="icon-button">
281+
{editIcon}
282+
</button>
283+
) : null}
284+
{' '}from <code className="branch-tag">{head}</code>
277285
</>) : null}
278286
</div>
279287
</div>

0 commit comments

Comments
 (0)