@@ -22,13 +22,33 @@ interface PendingCheckoutPayload {
2222 timestamp : number ; // epoch millis when the pending checkout was stored
2323}
2424
25+ function withCheckoutProgress < T > ( owner : string , repo : string , prNumber : number , task : ( progress : vscode . Progress < { message ?: string ; increment ?: number } > , token : vscode . CancellationToken ) => Promise < T > ) : Promise < T > {
26+ return vscode . window . withProgress ( {
27+ location : vscode . ProgressLocation . Notification ,
28+ title : vscode . l10n . t ( 'Checking out pull request #{0} from {1}/{2}...' , prNumber , owner , repo ) ,
29+ cancellable : true
30+ } , async ( progress , token ) => {
31+ if ( token . isCancellationRequested ) {
32+ return Promise . resolve ( undefined as unknown as T ) ;
33+ }
34+ return task ( progress , token ) ;
35+ } ) as Promise < T > ;
36+ }
37+
2538async function performPullRequestCheckout ( folderManager : FolderRepositoryManager , owner : string , repo : string , prNumber : number ) : Promise < void > {
2639 try {
2740 const pullRequest = await folderManager . resolvePullRequest ( owner , repo , prNumber ) ;
2841 if ( ! pullRequest ) {
42+ vscode . window . showErrorMessage ( vscode . l10n . t ( 'Pull request #{0} not found in {1}/{2}.' , prNumber , owner , repo ) ) ;
2943 Logger . warn ( `Pull request #${ prNumber } not found for checkout.` , UriHandler . ID ) ;
3044 return ;
3145 }
46+
47+ const proceed = await showCheckoutPrompt ( owner , repo , prNumber ) ;
48+ if ( ! proceed ) {
49+ return ;
50+ }
51+
3252 await vscode . commands . executeCommand ( 'pr.pick' , pullRequest ) ;
3353 } catch ( e ) {
3454 Logger . error ( `Error during pull request checkout: ${ e instanceof Error ? e . message : String ( e ) } ` , UriHandler . ID ) ;
@@ -48,11 +68,11 @@ export async function resumePendingCheckout(context: vscode.ExtensionContext, re
4868 return ;
4969 }
5070 const attempt = async ( ) => {
51- const fm = reposManager . getManagerForRepository ( pending . owner , pending . repo ) ;
52- if ( ! fm ) {
71+ const folderManager = reposManager . getManagerForRepository ( pending . owner , pending . repo ) ;
72+ if ( ! folderManager ) {
5373 return false ;
5474 }
55- await performPullRequestCheckout ( fm , pending . owner , pending . repo , pending . pullRequestNumber ) ;
75+ await performPullRequestCheckout ( folderManager , pending . owner , pending . repo , pending . pullRequestNumber ) ;
5676 await context . globalState . update ( PENDING_CHECKOUT_PULL_REQUEST_KEY , undefined ) ;
5777 return true ;
5878 } ;
@@ -65,6 +85,13 @@ export async function resumePendingCheckout(context: vscode.ExtensionContext, re
6585 }
6686}
6787
88+ export async function showCheckoutPrompt ( owner : string , repo : string , prNumber : number ) : Promise < boolean > {
89+ const message = vscode . l10n . t ( 'Checkout pull request #{0} from {1}/{2}?' , prNumber , owner , repo ) ;
90+ const confirm = vscode . l10n . t ( 'Checkout' ) ;
91+ const selection = await vscode . window . showInformationMessage ( message , { modal : true } , confirm ) ;
92+ return selection === confirm ;
93+ }
94+
6895export class UriHandler implements vscode . UriHandler {
6996 public static readonly ID = 'UriHandler' ;
7097 constructor ( private readonly _reposManagers : RepositoriesManager ,
@@ -111,6 +138,12 @@ export class UriHandler implements vscode.UriHandler {
111138 return PullRequestOverviewPanel . createOrShow ( this . _telemetry , this . _context . extensionUri , folderManager , pullRequest ) ;
112139 }
113140
141+ private async _savePendingCheckoutAndOpenFolder ( params : { owner : string ; repo : string ; pullRequestNumber : number } , folderUri : vscode . Uri ) : Promise < void > {
142+ const payload : PendingCheckoutPayload = { ...params , timestamp : Date . now ( ) } ;
143+ await this . _context . globalState . update ( PENDING_CHECKOUT_PULL_REQUEST_KEY , payload ) ;
144+ await vscode . commands . executeCommand ( 'vscode.openFolder' , folderUri ) ;
145+ }
146+
114147 private async _checkoutPullRequest ( uri : vscode . Uri ) : Promise < void > {
115148 const params = fromOpenOrCheckoutPullRequestWebviewUri ( uri ) ;
116149 if ( ! params ) {
@@ -121,18 +154,47 @@ export class UriHandler implements vscode.UriHandler {
121154 return performPullRequestCheckout ( folderManager , params . owner , params . repo , params . pullRequestNumber ) ;
122155 }
123156 // Folder not found; request workspace open then resume later.
124- try {
125- const remoteUri = vscode . Uri . parse ( `https://github.com/${ params . owner } /${ params . repo } ` ) ;
126- const workspaces = await this . _git . getRepositoryWorkspace ( remoteUri ) ;
127- if ( workspaces && workspaces . length ) {
128- const payload : PendingCheckoutPayload = { ...params , timestamp : Date . now ( ) } ;
129- await this . _context . globalState . update ( PENDING_CHECKOUT_PULL_REQUEST_KEY , payload ) ;
130- await vscode . commands . executeCommand ( 'vscode.openFolder' , workspaces [ 0 ] ) ;
131- } else {
132- Logger . warn ( `No repository workspace found for ${ remoteUri . toString ( ) } ` , UriHandler . ID ) ;
157+ await withCheckoutProgress ( params . owner , params . repo , params . pullRequestNumber , async ( progress , token ) => {
158+ if ( token . isCancellationRequested ) {
159+ return ;
160+ }
161+ try {
162+ progress . report ( { message : vscode . l10n . t ( 'Locating workspace...' ) } ) ;
163+ const remoteUri = vscode . Uri . parse ( `https://github.com/${ params . owner } /${ params . repo } ` ) ;
164+ const workspaces = await this . _git . getRepositoryWorkspace ( remoteUri ) ;
165+ if ( token . isCancellationRequested ) {
166+ return ;
167+ }
168+ if ( workspaces && workspaces . length ) {
169+ progress . report ( { message : vscode . l10n . t ( 'Opening workspace...' ) } ) ;
170+ await this . _savePendingCheckoutAndOpenFolder ( params , workspaces [ 0 ] ) ;
171+ } else {
172+ this . _showCloneOffer ( remoteUri , params ) ;
173+ }
174+ } catch ( e ) {
175+ Logger . error ( `Failed attempting workspace open for checkout PR: ${ e instanceof Error ? e . message : String ( e ) } ` , UriHandler . ID ) ;
176+ }
177+ } ) ;
178+ }
179+
180+ private async _showCloneOffer ( remoteUri : vscode . Uri , params : { owner : string ; repo : string ; pullRequestNumber : number } ) : Promise < void > {
181+ const cloneLabel = vscode . l10n . t ( 'Clone Repository' ) ;
182+ const choice = await vscode . window . showErrorMessage (
183+ vscode . l10n . t ( 'Could not find a folder for repository {0}/{1}. Please clone or open the repository manually.' , params . owner , params . repo ) ,
184+ cloneLabel
185+ ) ;
186+ Logger . warn ( `No repository workspace found for ${ remoteUri . toString ( ) } ` , UriHandler . ID ) ;
187+ if ( choice === cloneLabel ) {
188+ try {
189+ const clonedWorkspaceUri = await this . _git . clone ( remoteUri , { postCloneAction : 'none' } ) ;
190+ if ( clonedWorkspaceUri ) {
191+ await this . _savePendingCheckoutAndOpenFolder ( params , clonedWorkspaceUri ) ;
192+ } else {
193+ Logger . warn ( `Clone API returned null for ${ remoteUri . toString ( ) } ` , UriHandler . ID ) ;
194+ }
195+ } catch ( err ) {
196+ Logger . error ( `Failed to clone repository via API: ${ err instanceof Error ? err . message : String ( err ) } ` , UriHandler . ID ) ;
133197 }
134- } catch ( e ) {
135- Logger . error ( `Failed attempting workspace open for checkout PR: ${ e instanceof Error ? e . message : String ( e ) } ` , UriHandler . ID ) ;
136198 }
137199 }
138200
0 commit comments