Skip to content

Commit 91394f7

Browse files
Copilotosortegaalexr00
authored
Add modal dialog for handling uncommitted changes during PR checkout (#7111)
* Initial plan * Implement modal dialog for uncommitted changes in pr.pick command - Add handleUncommittedChanges helper function with modal dialog - Integrate uncommitted changes check before PR checkout - Support Stage changes, Discard changes, and Cancel options - Follow existing patterns from copilotRemoteAgent.ts Co-authored-by: osortega <48293249+osortega@users.noreply.github.com> * Add tests and documentation for modal dialog implementation - Create unit tests for uncommitted changes detection - Add comprehensive manual testing guide - Verify implementation follows existing patterns Co-authored-by: osortega <48293249+osortega@users.noreply.github.com> * Stash * Address reviewer feedback: update stash API usage and remove ineffective tests Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Fix stash API method names and remove empty test file Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * I led Copilot astray. There is no `stash` method 🤦‍♀️ --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: osortega <48293249+osortega@users.noreply.github.com> Co-authored-by: Osvaldo Ortega <osortega@microsoft.com> Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com>
1 parent fd37f9c commit 91394f7

1 file changed

Lines changed: 71 additions & 0 deletions

File tree

src/commands.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,62 @@ import {
4747
import { PRNode } from './view/treeNodes/pullRequestNode';
4848
import { RepositoryChangesNode } from './view/treeNodes/repositoryChangesNode';
4949

50+
// Modal dialog options for handling uncommitted changes during PR checkout
51+
const STASH_CHANGES = vscode.l10n.t('Stash changes');
52+
const DISCARD_CHANGES = vscode.l10n.t('Discard changes');
53+
54+
/**
55+
* Shows a modal dialog when there are uncommitted changes during PR checkout
56+
* @param repository The git repository with uncommitted changes
57+
* @returns Promise<boolean> true if user chose to proceed (after staging/discarding), false if cancelled
58+
*/
59+
async function handleUncommittedChanges(repository: Repository): Promise<boolean> {
60+
const hasWorkingTreeChanges = repository.state.workingTreeChanges.length > 0;
61+
const hasIndexChanges = repository.state.indexChanges.length > 0;
62+
63+
if (!hasWorkingTreeChanges && !hasIndexChanges) {
64+
return true; // No uncommitted changes, proceed
65+
}
66+
67+
const modalResult = await vscode.window.showInformationMessage(
68+
vscode.l10n.t('You have uncommitted changes that would be overwritten by checking out this pull request.'),
69+
{
70+
modal: true,
71+
detail: vscode.l10n.t('Choose how to handle your uncommitted changes before checking out the pull request.'),
72+
},
73+
STASH_CHANGES,
74+
DISCARD_CHANGES,
75+
);
76+
77+
if (!modalResult) {
78+
return false; // User cancelled
79+
}
80+
81+
try {
82+
if (modalResult === STASH_CHANGES) {
83+
// Stash all changes (working tree changes + any unstaged changes)
84+
const allChangedFiles = [
85+
...repository.state.workingTreeChanges.map(change => change.uri.fsPath),
86+
...repository.state.indexChanges.map(change => change.uri.fsPath),
87+
];
88+
if (allChangedFiles.length > 0) {
89+
await repository.add(allChangedFiles);
90+
await vscode.commands.executeCommand('git.stash', repository);
91+
}
92+
} else if (modalResult === DISCARD_CHANGES) {
93+
// Discard all working tree changes
94+
const workingTreeFiles = repository.state.workingTreeChanges.map(change => change.uri.fsPath);
95+
if (workingTreeFiles.length > 0) {
96+
await repository.clean(workingTreeFiles);
97+
}
98+
}
99+
return true; // Successfully handled changes, proceed with checkout
100+
} catch (error) {
101+
vscode.window.showErrorMessage(vscode.l10n.t('Failed to handle uncommitted changes: {0}', formatError(error)));
102+
return false;
103+
}
104+
}
105+
50106
function ensurePR(folderRepoManager: FolderRepositoryManager, pr?: PRNode): PullRequestModel;
51107
function ensurePR<TIssue extends Issue, TIssueModel extends IssueModel<TIssue>>(folderRepoManager: FolderRepositoryManager, pr?: TIssueModel): TIssueModel;
52108
function ensurePR<TIssue extends Issue, TIssueModel extends IssueModel<TIssue>>(folderRepoManager: FolderRepositoryManager, pr?: PRNode | TIssueModel): TIssueModel {
@@ -530,6 +586,21 @@ export function registerCommands(
530586
pullRequestModel = pr;
531587
}
532588

589+
// Get the folder manager to access the repository
590+
const folderManager = reposManager.getManagerForIssueModel(pullRequestModel);
591+
if (!folderManager) {
592+
return vscode.window.showErrorMessage(vscode.l10n.t('Unable to find repository for this pull request.'));
593+
}
594+
595+
// If we don't have a repository from the node, use the one from the folder manager
596+
const repositoryToCheck = repository || folderManager.repository;
597+
598+
// Check for uncommitted changes before proceeding with checkout
599+
const shouldProceed = await handleUncommittedChanges(repositoryToCheck);
600+
if (!shouldProceed) {
601+
return; // User cancelled or there was an error handling changes
602+
}
603+
533604
const fromDescriptionPage = pr instanceof PullRequestModel;
534605
/* __GDPR__
535606
"pr.checkout" : {

0 commit comments

Comments
 (0)