From a56035b52dccbc1812f77efbf4985709f620036d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 10:26:53 +0000 Subject: [PATCH 1/4] Initial plan From e7a34ada203e7f2c2ca8038bf380992130531e4f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 10:33:37 +0000 Subject: [PATCH 2/4] Initial plan for auto-stash on PR checkout feature Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- .../vscode.proposed.chatParticipantAdditions.d.ts | 11 +++++++++-- .../vscode.proposed.chatParticipantPrivate.d.ts | 7 +++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index 2cb4168b43..f5b8bd0072 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -103,6 +103,7 @@ declare module 'vscode' { isConfirmed?: boolean; isComplete?: boolean; toolSpecificData?: ChatTerminalToolInvocationData; + fromSubAgent?: boolean; constructor(toolName: string, toolCallId: string, isError?: boolean); } @@ -646,7 +647,13 @@ declare module 'vscode' { } export interface ChatRequest { - modeInstructions?: string; - modeInstructionsToolReferences?: readonly ChatLanguageModelToolReference[]; + readonly modeInstructions?: string; + readonly modeInstructions2?: ChatRequestModeInstructions; + } + + export interface ChatRequestModeInstructions { + readonly content: string; + readonly toolReferences?: readonly ChatLanguageModelToolReference[]; + readonly metadata?: Record; } } diff --git a/src/@types/vscode.proposed.chatParticipantPrivate.d.ts b/src/@types/vscode.proposed.chatParticipantPrivate.d.ts index eb42d52c92..ade1cf62e8 100644 --- a/src/@types/vscode.proposed.chatParticipantPrivate.d.ts +++ b/src/@types/vscode.proposed.chatParticipantPrivate.d.ts @@ -187,6 +187,8 @@ declare module 'vscode' { isQuotaExceeded?: boolean; + isRateLimited?: boolean; + level?: ChatErrorLevel; code?: string; @@ -219,6 +221,10 @@ declare module 'vscode' { chatSessionId?: string; chatInteractionId?: string; terminalCommand?: string; + /** + * Lets us add some nicer UI to toolcalls that came from a sub-agent, but in the long run, this should probably just be rendered in a similar way to thinking text + tool call groups + */ + fromSubAgent?: boolean; } export interface LanguageModelToolInvocationPrepareOptions { @@ -239,6 +245,7 @@ declare module 'vscode' { export class ExtendedLanguageModelToolResult extends LanguageModelToolResult { toolResultMessage?: string | MarkdownString; toolResultDetails?: Array; + toolMetadata?: unknown; } // #region Chat participant detection From 30a8977e961f17296f1abb602927a5f15513ce53 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 10:48:34 +0000 Subject: [PATCH 3/4] Add auto-stash on PR checkout feature - Add autoStashOnCheckout setting - Implement auto-stash logic when checking out PR - Pop stash when returning to default branch Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- package.json | 5 ++++ package.nls.json | 1 + src/common/settingKeys.ts | 1 + src/github/activityBarViewProvider.ts | 13 +++++++++++ src/github/folderRepositoryManager.ts | 14 ++++++++++++ src/github/pullRequestOverview.ts | 12 ++++++++++ src/view/reviewManager.ts | 33 +++++++++++++++++++++++++++ 7 files changed, 79 insertions(+) diff --git a/package.json b/package.json index 7595953681..1e58bd7081 100644 --- a/package.json +++ b/package.json @@ -462,6 +462,11 @@ "%githubPullRequests.postDone.checkoutDefaultBranchAndPull%" ] }, + "githubPullRequests.autoStashOnCheckout": { + "type": "boolean", + "description": "%githubPullRequests.autoStashOnCheckout.description%", + "default": false + }, "githubPullRequests.defaultCommentType": { "type": "string", "enum": [ diff --git a/package.nls.json b/package.nls.json index 59e94e4b82..0590792e6a 100644 --- a/package.nls.json +++ b/package.nls.json @@ -80,6 +80,7 @@ "githubPullRequests.postDone.description": "The action to take after using the 'checkout default branch' or 'delete branch' actions on a currently checked out pull request.", "githubPullRequests.postDone.checkoutDefaultBranch": "Checkout the default branch of the repository", "githubPullRequests.postDone.checkoutDefaultBranchAndPull": "Checkout the default branch of the repository and pull the latest changes", + "githubPullRequests.autoStashOnCheckout.description": "Automatically stash changes when checking out a pull request, and pop the stash when checking out the default branch.", "githubPullRequests.defaultCommentType.description": "The default comment type to use when submitting a comment and there is no active review", "githubPullRequests.defaultCommentType.single": "Submits the comment as a single comment that will be immediately visible to other users", "githubPullRequests.defaultCommentType.review": "Submits the comment as a review comment that will be visible to other users once the review is submitted", diff --git a/src/common/settingKeys.ts b/src/common/settingKeys.ts index fa1fb63b60..ec2073bb52 100644 --- a/src/common/settingKeys.ts +++ b/src/common/settingKeys.ts @@ -37,6 +37,7 @@ export type PullPRBranchVariants = 'never' | 'pull' | 'pullAndMergeBase' | 'pull export const UPSTREAM_REMOTE = 'upstreamRemote'; export const DEFAULT_CREATE_OPTION = 'defaultCreateOption'; export const CREATE_BASE_BRANCH = 'createDefaultBaseBranch'; +export const AUTO_STASH_ON_CHECKOUT = 'autoStashOnCheckout'; export const ISSUES_SETTINGS_NAMESPACE = 'githubIssues'; export const ASSIGN_WHEN_WORKING = 'assignWhenWorking'; diff --git a/src/github/activityBarViewProvider.ts b/src/github/activityBarViewProvider.ts index 7125c35b38..2414b0339b 100644 --- a/src/github/activityBarViewProvider.ts +++ b/src/github/activityBarViewProvider.ts @@ -8,6 +8,7 @@ import { openPullRequestOnGitHub } from '../commands'; import { IComment } from '../common/comment'; import { emojify, ensureEmojis } from '../common/emoji'; import { disposeAll } from '../common/lifecycle'; +import Logger from '../common/logger'; import { ReviewEvent } from '../common/timelineEvent'; import { formatError } from '../common/utils'; import { getNonce, IRequestMessage, WebviewViewBase } from '../common/webview'; @@ -130,7 +131,19 @@ export class PullRequestViewProvider extends WebviewViewBase implements vscode.W try { const defaultBranch = await this._folderRepositoryManager.getPullRequestRepositoryDefaultBranch(this._item); const prBranch = this._folderRepositoryManager.repository.state.HEAD?.name; + const shouldPopStash = this._folderRepositoryManager.stashedOnCheckout; await this._folderRepositoryManager.checkoutDefaultBranch(defaultBranch); + if (shouldPopStash) { + try { + Logger.appendLine('Popping stash after returning to default branch', 'ActivityBarViewProvider'); + await vscode.commands.executeCommand('git.stashPop', this._folderRepositoryManager.repository); + this._folderRepositoryManager.stashedOnCheckout = false; + Logger.appendLine('Stash popped successfully', 'ActivityBarViewProvider'); + } catch (popError) { + Logger.error(`Failed to pop stash: ${formatError(popError)}`, 'ActivityBarViewProvider'); + vscode.window.showWarningMessage(vscode.l10n.t('Failed to restore stashed changes: {0}', formatError(popError))); + } + } if (prBranch) { await this._folderRepositoryManager.cleanupAfterPullRequest(prBranch, this._item); } diff --git a/src/github/folderRepositoryManager.ts b/src/github/folderRepositoryManager.ts index 41e4585be3..22984365fb 100644 --- a/src/github/folderRepositoryManager.ts +++ b/src/github/folderRepositoryManager.ts @@ -209,6 +209,12 @@ export class FolderRepositoryManager extends Disposable { private _onDidChangeRepositories = this._register(new vscode.EventEmitter<{ added: boolean }>()); readonly onDidChangeRepositories: vscode.Event<{ added: boolean }> = this._onDidChangeRepositories.event; + /** + * Tracks whether changes were stashed when checking out the current PR. + * Used to determine if we should pop the stash when returning to the default branch. + */ + private _stashedOnCheckout: boolean = false; + private _onDidChangeAssignableUsers = this._register(new vscode.EventEmitter()); readonly onDidChangeAssignableUsers: vscode.Event = this._onDidChangeAssignableUsers.event; @@ -378,6 +384,14 @@ export class FolderRepositoryManager extends Disposable { this._onDidChangeActivePullRequest.fire({ old: oldPR, new: pullRequest }); } + get stashedOnCheckout(): boolean { + return this._stashedOnCheckout; + } + + set stashedOnCheckout(value: boolean) { + this._stashedOnCheckout = value; + } + get repository(): Repository { return this._repository; } diff --git a/src/github/pullRequestOverview.ts b/src/github/pullRequestOverview.ts index 491405347a..4962471f15 100644 --- a/src/github/pullRequestOverview.ts +++ b/src/github/pullRequestOverview.ts @@ -652,7 +652,19 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel): Promise { try { const prBranch = this._folderRepositoryManager.repository.state.HEAD?.name; + const shouldPopStash = this._folderRepositoryManager.stashedOnCheckout; await this._folderRepositoryManager.checkoutDefaultBranch(message.args); + if (shouldPopStash) { + try { + Logger.appendLine('Popping stash after returning to default branch', PullRequestOverviewPanel.ID); + await vscode.commands.executeCommand('git.stashPop', this._folderRepositoryManager.repository); + this._folderRepositoryManager.stashedOnCheckout = false; + Logger.appendLine('Stash popped successfully', PullRequestOverviewPanel.ID); + } catch (popError) { + Logger.error(`Failed to pop stash: ${formatError(popError)}`, PullRequestOverviewPanel.ID); + vscode.window.showWarningMessage(vscode.l10n.t('Failed to restore stashed changes: {0}', formatError(popError))); + } + } if (prBranch) { await this._folderRepositoryManager.cleanupAfterPullRequest(prBranch, this._item); } diff --git a/src/view/reviewManager.ts b/src/view/reviewManager.ts index 5b30eb3418..21293343cb 100644 --- a/src/view/reviewManager.ts +++ b/src/view/reviewManager.ts @@ -15,6 +15,7 @@ import { Disposable, disposeAll, toDisposable } from '../common/lifecycle'; import Logger from '../common/logger'; import { parseRepositoryRemotes, Remote } from '../common/remote'; import { + AUTO_STASH_ON_CHECKOUT, COMMENTS, FOCUSED_MODE, IGNORE_PR_BRANCHES, @@ -1087,6 +1088,38 @@ export class ReviewManager extends Disposable { this.statusBarItem.show(); this.switchingToReviewMode = true; + // Check if auto-stash is enabled and there are uncommitted changes + const autoStashEnabled = vscode.workspace.getConfiguration(PR_SETTINGS_NAMESPACE).get(AUTO_STASH_ON_CHECKOUT, false); + this._folderRepoManager.stashedOnCheckout = false; + + if (autoStashEnabled) { + const workingTreeChanges = this._repository.state.workingTreeChanges; + const indexChanges = this._repository.state.indexChanges; + const hasChanges = workingTreeChanges.length > 0 || indexChanges.length > 0; + + if (hasChanges) { + try { + Logger.appendLine('Auto-stashing changes before PR checkout', this.id); + // Add all tracked changes to staging area + const allChangedFiles = [ + ...workingTreeChanges.map(change => change.uri.fsPath), + ...indexChanges.map(change => change.uri.fsPath), + ]; + if (allChangedFiles.length > 0) { + await this._repository.add(allChangedFiles); + } + // Stash the changes + await vscode.commands.executeCommand('git.stash', this._repository); + this._folderRepoManager.stashedOnCheckout = true; + Logger.appendLine('Changes stashed successfully', this.id); + } catch (stashError) { + Logger.error(`Failed to auto-stash changes: ${formatError(stashError)}`, this.id); + // Don't block checkout if stashing fails, but warn the user + vscode.window.showWarningMessage(vscode.l10n.t('Failed to auto-stash changes: {0}', formatError(stashError))); + } + } + } + try { await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification }, async (progress) => { const didLocalCheckout = await this._folderRepoManager.checkoutExistingPullRequestBranch(pr, progress); From 942024702987880456d834e7dbdaf71168d4825d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 10:53:11 +0000 Subject: [PATCH 4/4] Address code review feedback - Fix redundant check for empty files - Don't re-add already staged index changes - Fix race condition by reading stash flag after checkout Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/github/activityBarViewProvider.ts | 3 ++- src/github/pullRequestOverview.ts | 3 ++- src/view/reviewManager.ts | 11 ++++------- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/github/activityBarViewProvider.ts b/src/github/activityBarViewProvider.ts index 2414b0339b..dd2e73b16a 100644 --- a/src/github/activityBarViewProvider.ts +++ b/src/github/activityBarViewProvider.ts @@ -131,8 +131,9 @@ export class PullRequestViewProvider extends WebviewViewBase implements vscode.W try { const defaultBranch = await this._folderRepositoryManager.getPullRequestRepositoryDefaultBranch(this._item); const prBranch = this._folderRepositoryManager.repository.state.HEAD?.name; - const shouldPopStash = this._folderRepositoryManager.stashedOnCheckout; await this._folderRepositoryManager.checkoutDefaultBranch(defaultBranch); + // Check if we should pop the stash after successful checkout + const shouldPopStash = this._folderRepositoryManager.stashedOnCheckout; if (shouldPopStash) { try { Logger.appendLine('Popping stash after returning to default branch', 'ActivityBarViewProvider'); diff --git a/src/github/pullRequestOverview.ts b/src/github/pullRequestOverview.ts index 4962471f15..3bdc383625 100644 --- a/src/github/pullRequestOverview.ts +++ b/src/github/pullRequestOverview.ts @@ -652,8 +652,9 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel): Promise { try { const prBranch = this._folderRepositoryManager.repository.state.HEAD?.name; - const shouldPopStash = this._folderRepositoryManager.stashedOnCheckout; await this._folderRepositoryManager.checkoutDefaultBranch(message.args); + // Check if we should pop the stash after successful checkout + const shouldPopStash = this._folderRepositoryManager.stashedOnCheckout; if (shouldPopStash) { try { Logger.appendLine('Popping stash after returning to default branch', PullRequestOverviewPanel.ID); diff --git a/src/view/reviewManager.ts b/src/view/reviewManager.ts index 21293343cb..a124854989 100644 --- a/src/view/reviewManager.ts +++ b/src/view/reviewManager.ts @@ -1100,13 +1100,10 @@ export class ReviewManager extends Disposable { if (hasChanges) { try { Logger.appendLine('Auto-stashing changes before PR checkout', this.id); - // Add all tracked changes to staging area - const allChangedFiles = [ - ...workingTreeChanges.map(change => change.uri.fsPath), - ...indexChanges.map(change => change.uri.fsPath), - ]; - if (allChangedFiles.length > 0) { - await this._repository.add(allChangedFiles); + // Add only working tree changes to staging area (indexChanges are already staged) + if (workingTreeChanges.length > 0) { + const workingTreeFiles = workingTreeChanges.map(change => change.uri.fsPath); + await this._repository.add(workingTreeFiles); } // Stash the changes await vscode.commands.executeCommand('git.stash', this._repository);