@@ -67,6 +67,7 @@ export function registerCloneReposCommand(context: vscode.ExtensionContext): voi
6767 }
6868
6969 // Clone repositories with progress
70+ const authHeader = await getGithubAuthHeader ( validStudents ) ;
7071 let successCount = 0 ;
7172 let failureCount = 0 ;
7273 const failures : { student : string ; error : string } [ ] = [ ] ;
@@ -118,7 +119,7 @@ export function registerCloneReposCommand(context: vscode.ExtensionContext): voi
118119 fs . mkdirSync ( repoFolder , { recursive : true } ) ;
119120
120121 // Clone repository into the repo subfolder
121- await cloneRepository ( student . repositorio , repoFolder ) ;
122+ await cloneRepository ( student . repositorio , repoFolder , authHeader ) ;
122123
123124 successCount ++ ;
124125
@@ -162,13 +163,18 @@ export function registerCloneReposCommand(context: vscode.ExtensionContext): voi
162163/**
163164 * Clones a git repository to the target path
164165 */
165- async function cloneRepository ( repoUrl : string , targetPath : string ) : Promise < void > {
166+ async function cloneRepository ( repoUrl : string , targetPath : string , authHeader ?: string ) : Promise < void > {
166167 return new Promise ( ( resolve , reject ) => {
167- const { exec } = require ( 'child_process' ) ;
168+ const { execFile } = require ( 'child_process' ) ;
169+ const args : string [ ] = [ ] ;
168170
169- const command = `git clone "${ repoUrl } " "${ targetPath } "` ;
171+ if ( authHeader && isGithubHttpsRepo ( repoUrl ) ) {
172+ args . push ( '-c' , `http.extraHeader=${ authHeader } ` ) ;
173+ }
174+
175+ args . push ( 'clone' , repoUrl , targetPath ) ;
170176
171- exec ( command , ( error : Error | null , _stdout : string , stderr : string ) => {
177+ execFile ( 'git' , args , { env : { ... process . env , GIT_TERMINAL_PROMPT : '0' } } , ( error : Error | null , _stdout : string , stderr : string ) => {
172178 if ( error ) {
173179 reject ( new Error ( stderr || error . message ) ) ;
174180 return ;
@@ -177,3 +183,33 @@ async function cloneRepository(repoUrl: string, targetPath: string): Promise<voi
177183 } ) ;
178184 } ) ;
179185}
186+
187+ function isGithubHttpsRepo ( repoUrl : string ) : boolean {
188+ try {
189+ const parsed = new URL ( repoUrl ) ;
190+ return parsed . protocol === 'https:' && parsed . hostname . toLowerCase ( ) === 'github.com' ;
191+ } catch {
192+ return false ;
193+ }
194+ }
195+
196+ async function getGithubAuthHeader ( students : StudentSubmission [ ] ) : Promise < string | undefined > {
197+ const needsGithubAuth = students . some ( student => isGithubHttpsRepo ( student . repositorio ) ) ;
198+
199+ if ( ! needsGithubAuth ) {
200+ return undefined ;
201+ }
202+
203+ try {
204+ const session = await vscode . authentication . getSession ( 'github' , [ 'repo' ] , { createIfNone : true } ) ;
205+ if ( ! session ?. accessToken ) {
206+ return undefined ;
207+ }
208+
209+ const basic = Buffer . from ( `x-access-token:${ session . accessToken } ` ) . toString ( 'base64' ) ;
210+ return `AUTHORIZATION: basic ${ basic } ` ;
211+ } catch {
212+ vscode . window . showWarningMessage ( 'GitHub sign-in was skipped. Private repositories may fail to clone.' ) ;
213+ return undefined ;
214+ }
215+ }
0 commit comments