@@ -260,16 +260,28 @@ pub fn get_java_major_version(java_executable: &PathBuf) -> zed::Result<u32> {
260260/// * Could not fetch tags from Github
261261/// * Failed to deserialize response
262262/// * Unexpected Github response format
263- pub fn get_latest_versions_from_tag ( repo : & str ) -> zed:: Result < ( String , Option < String > ) > {
263+ pub fn get_latest_versions_from_tag (
264+ repo : & str ,
265+ worktree : & Worktree ,
266+ ) -> zed:: Result < ( String , Option < String > ) > {
267+ let mut request = HttpRequest :: builder ( )
268+ . method ( HttpMethod :: Get )
269+ . url ( format ! ( "https://api.github.com/repos/{repo}/tags" ) ) ;
270+
271+ // Use GITHUB_TOKEN or GH_TOKEN environment variable if available
272+ // to avoid GitHub API rate limiting (60 req/hr unauthenticated vs 5000/hr authenticated).
273+ if let Some ( token) = github_token ( & worktree. shell_env ( ) ) {
274+ request = request. header ( "Authorization" , format ! ( "token {token}" ) ) ;
275+ }
276+
277+ let request = request
278+ . build ( )
279+ . map_err ( |err| format ! ( "{TAG_RETRIEVAL_ERROR}: {err}" ) ) ?;
280+
264281 let tags_response_body = serde_json:: from_slice :: < Value > (
265- & fetch (
266- & HttpRequest :: builder ( )
267- . method ( HttpMethod :: Get )
268- . url ( format ! ( "https://api.github.com/repos/{repo}/tags" ) )
269- . build ( ) ?,
270- )
271- . map_err ( |err| format ! ( "{TAG_RETRIEVAL_ERROR}: {err}" ) ) ?
272- . body ,
282+ & fetch ( & request)
283+ . map_err ( |err| format ! ( "{TAG_RETRIEVAL_ERROR}: {err}" ) ) ?
284+ . body ,
273285 )
274286 . map_err ( |err| format ! ( "{TAG_RESPONSE_ERROR}: {err}" ) ) ?;
275287
@@ -284,6 +296,15 @@ pub fn get_latest_versions_from_tag(repo: &str) -> zed::Result<(String, Option<S
284296 ) )
285297}
286298
299+ fn github_token ( shell_env : & [ ( String , String ) ] ) -> Option < & str > {
300+ [ "GITHUB_TOKEN" , "GH_TOKEN" ] . into_iter ( ) . find_map ( |name| {
301+ shell_env
302+ . iter ( )
303+ . find ( |( key, value) | key == name && !value. is_empty ( ) )
304+ . map ( |( _, value) | value. as_str ( ) )
305+ } )
306+ }
307+
287308fn get_tag_at ( github_tags : & Value , index : usize ) -> Option < & str > {
288309 github_tags. as_array ( ) . and_then ( |tag| {
289310 tag. get ( index) . and_then ( |latest_tag| {
@@ -491,6 +512,33 @@ mod tests {
491512 args : ArgsStringOrList ,
492513 }
493514
515+ #[ test]
516+ fn github_token_prefers_github_token ( ) {
517+ let env = vec ! [
518+ ( "GH_TOKEN" . to_string( ) , "gh" . to_string( ) ) ,
519+ ( "GITHUB_TOKEN" . to_string( ) , "github" . to_string( ) ) ,
520+ ] ;
521+
522+ assert_eq ! ( github_token( & env) , Some ( "github" ) ) ;
523+ }
524+
525+ #[ test]
526+ fn github_token_falls_back_to_gh_token ( ) {
527+ let env = vec ! [
528+ ( "GITHUB_TOKEN" . to_string( ) , String :: new( ) ) ,
529+ ( "GH_TOKEN" . to_string( ) , "gh" . to_string( ) ) ,
530+ ] ;
531+
532+ assert_eq ! ( github_token( & env) , Some ( "gh" ) ) ;
533+ }
534+
535+ #[ test]
536+ fn github_token_ignores_missing_and_empty_tokens ( ) {
537+ let env = vec ! [ ( "GITHUB_TOKEN" . to_string( ) , String :: new( ) ) ] ;
538+
539+ assert_eq ! ( github_token( & env) , None ) ;
540+ }
541+
494542 #[ test]
495543 fn test_args_list_with_spaces_quotes_elements ( ) {
496544 let json = std:: fs:: read_to_string ( "testdata/args_with_spaces.json" ) . unwrap ( ) ;
0 commit comments