@@ -159,8 +159,13 @@ public function clone_repo( string $url, ?string $name = null, array $options =
159159 $ started_at
160160 );
161161
162+ $ env = $ this ->build_clone_environment ( $ url , $ options );
163+ if ( is_wp_error ( $ env ) ) {
164+ return $ env ;
165+ }
166+
162167 $ command = $ this ->build_clone_command ( $ url , $ repo_path , $ partial_clone );
163- $ result = $ this ->run_clone_command ( $ command , $ progress_callback , $ started_at );
168+ $ result = $ this ->run_clone_command ( $ command , $ progress_callback , $ started_at, $ env );
164169
165170 if ( is_wp_error ( $ result ) ) {
166171 return $ this ->clone_failed_error ( $ result , $ name , $ repo_path , $ url );
@@ -196,6 +201,46 @@ private function build_clone_command( string $url, string $repo_path, bool $part
196201 return 'GIT_TERMINAL_PROMPT=0 git ' . implode ( ' ' , $ args );
197202 }
198203
204+ /**
205+ * Build additional environment values for git clone.
206+ *
207+ * @param string $url Git clone URL.
208+ * @param array $options Optional clone options.
209+ * @return array<string,string>|null|\WP_Error Extra environment values, null for default environment, or error.
210+ */
211+ private function build_clone_environment ( string $ url , array $ options ): array |null |\WP_Error {
212+ $ auth_token_env = isset ( $ options ['auth_token_env ' ] ) && is_scalar ( $ options ['auth_token_env ' ] ) ? trim ( (string ) $ options ['auth_token_env ' ] ) : '' ;
213+ if ( '' === $ auth_token_env ) {
214+ return null ;
215+ }
216+
217+ if ( ! preg_match ( '/^[A-Za-z_][A-Za-z0-9_]*$/ ' , $ auth_token_env ) ) {
218+ return new \WP_Error ( 'invalid_auth_token_env ' , 'Clone auth token environment variable name is invalid. ' , array ( 'status ' => 400 ) );
219+ }
220+
221+ $ token = trim ( (string ) getenv ( $ auth_token_env ) );
222+ if ( '' === $ token ) {
223+ return new \WP_Error ( 'missing_auth_token_env ' , sprintf ( 'Clone auth token environment variable %s is empty or unavailable. ' , $ auth_token_env ), array ( 'status ' => 400 ) );
224+ }
225+
226+ $ parts = wp_parse_url ( $ url );
227+ $ host = is_array ( $ parts ) && isset ( $ parts ['host ' ] ) ? strtolower ( (string ) $ parts ['host ' ] ) : '' ;
228+ if ( '' === $ host ) {
229+ return new \WP_Error ( 'unsupported_auth_token_url ' , 'Clone auth token support requires an HTTPS repository URL. ' , array ( 'status ' => 400 ) );
230+ }
231+
232+ $ env = getenv ();
233+ if ( ! is_array ( $ env ) ) {
234+ $ env = array ();
235+ }
236+
237+ $ env ['GIT_CONFIG_COUNT ' ] = '1 ' ;
238+ $ env ['GIT_CONFIG_KEY_0 ' ] = sprintf ( 'http.https://%s/.extraheader ' , $ host );
239+ $ env ['GIT_CONFIG_VALUE_0 ' ] = 'AUTHORIZATION: bearer ' . $ token ;
240+
241+ return $ env ;
242+ }
243+
199244 /**
200245 * Remote HTTP(S) and SSH hosts generally support safe blobless clones; local
201246 * paths and file URLs often do not, and are usually test fixtures anyway.
@@ -215,14 +260,14 @@ private function should_use_partial_clone( string $url ): bool {
215260 * @param float $started_at Clone start timestamp.
216261 * @return array{success: true, output: string}|\WP_Error
217262 */
218- private function run_clone_command ( string $ command , ?callable $ progress_callback , float $ started_at ): array |\WP_Error {
263+ private function run_clone_command ( string $ command , ?callable $ progress_callback , float $ started_at, ? array $ env = null ): array |\WP_Error {
219264 $ descriptor_spec = array (
220265 1 => array ( 'pipe ' , 'w ' ),
221266 2 => array ( 'pipe ' , 'w ' ),
222267 );
223268
224269 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.system_calls_proc_open
225- $ process = proc_open ( $ command , $ descriptor_spec , $ pipes );
270+ $ process = proc_open ( $ command , $ descriptor_spec , $ pipes, null , $ env );
226271 if ( ! is_resource ( $ process ) ) {
227272 return new \WP_Error ( 'clone_failed ' , 'Git clone failed to start. ' , array ( 'status ' => 500 ) );
228273 }
0 commit comments