@@ -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 ,
@@ -116,23 +143,56 @@ export class UriHandler implements vscode.UriHandler {
116143 if ( ! params ) {
117144 return ;
118145 }
119- const folderManager = this . _reposManagers . getManagerForRepository ( params . owner , params . repo ) ;
120- if ( folderManager ) {
121- return performPullRequestCheckout ( folderManager , params . owner , params . repo , params . pullRequestNumber ) ;
122- }
123- // 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 ) ;
146+ await withCheckoutProgress ( params . owner , params . repo , params . pullRequestNumber , async ( progress , token ) => {
147+ if ( token . isCancellationRequested ) {
148+ return ;
149+ }
150+ const folderManager = this . _reposManagers . getManagerForRepository ( params . owner , params . repo ) ;
151+ if ( folderManager ) {
152+ return performPullRequestCheckout ( folderManager , params . owner , params . repo , params . pullRequestNumber ) ;
153+ }
154+ // Folder not found; request workspace open then resume later.
155+ try {
156+ progress . report ( { message : vscode . l10n . t ( 'Locating workspace...' ) } ) ;
157+ const remoteUri = vscode . Uri . parse ( `https://github.com/${ params . owner } /${ params . repo } ` ) ;
158+ const workspaces = await this . _git . getRepositoryWorkspace ( remoteUri ) ;
159+ if ( token . isCancellationRequested ) {
160+ return ;
161+ }
162+ if ( workspaces && workspaces . length ) {
163+ progress . report ( { message : vscode . l10n . t ( 'Opening workspace...' ) } ) ;
164+ const payload : PendingCheckoutPayload = { ...params , timestamp : Date . now ( ) } ;
165+ await this . _context . globalState . update ( PENDING_CHECKOUT_PULL_REQUEST_KEY , payload ) ;
166+ await vscode . commands . executeCommand ( 'vscode.openFolder' , workspaces [ 0 ] ) ;
167+ } else {
168+ this . _showCloneOffer ( remoteUri , params ) ;
169+ }
170+ } catch ( e ) {
171+ Logger . error ( `Failed attempting workspace open for checkout PR: ${ e instanceof Error ? e . message : String ( e ) } ` , UriHandler . ID ) ;
172+ }
173+ } ) ;
174+ }
175+
176+ private async _showCloneOffer ( remoteUri : vscode . Uri , params : { owner : string ; repo : string ; pullRequestNumber : number } ) : Promise < void > {
177+ const cloneLabel = vscode . l10n . t ( 'Clone Repository' ) ;
178+ const choice = await vscode . window . showErrorMessage (
179+ vscode . l10n . t ( 'Could not find a folder for repository {0}/{1}. Please clone or open the repository manually.' , params . owner , params . repo ) ,
180+ cloneLabel
181+ ) ;
182+ Logger . warn ( `No repository workspace found for ${ remoteUri . toString ( ) } ` , UriHandler . ID ) ;
183+ if ( choice === cloneLabel ) {
184+ try {
185+ const clonedWorkspaceUri = await this . _git . clone ( remoteUri , { postCloneAction : 'none' } ) ;
186+ if ( clonedWorkspaceUri ) {
187+ const payload : PendingCheckoutPayload = { ...params , timestamp : Date . now ( ) } ;
188+ await this . _context . globalState . update ( PENDING_CHECKOUT_PULL_REQUEST_KEY , payload ) ;
189+ await vscode . commands . executeCommand ( 'vscode.openFolder' , clonedWorkspaceUri ) ;
190+ } else {
191+ Logger . warn ( `Clone API returned null for ${ remoteUri . toString ( ) } ` , UriHandler . ID ) ;
192+ }
193+ } catch ( err ) {
194+ Logger . error ( `Failed to clone repository via API: ${ err instanceof Error ? err . message : String ( err ) } ` , UriHandler . ID ) ;
133195 }
134- } catch ( e ) {
135- Logger . error ( `Failed attempting workspace open for checkout PR: ${ e instanceof Error ? e . message : String ( e ) } ` , UriHandler . ID ) ;
136196 }
137197 }
138198
0 commit comments