11package parser
22
33import (
4+ "encoding/json"
45 "fmt"
56 "os"
67 "os/exec"
@@ -197,7 +198,9 @@ func resolveRefToSHA(owner, repo, ref string) (string, error) {
197198 if err != nil {
198199 outputStr := string (output )
199200 if strings .Contains (outputStr , "GH_TOKEN" ) || strings .Contains (outputStr , "authentication" ) || strings .Contains (outputStr , "not logged into" ) {
200- return "" , fmt .Errorf ("failed to resolve ref to SHA: GitHub authentication required. Please run 'gh auth login' or set GH_TOKEN/GITHUB_TOKEN environment variable: %w" , err )
201+ // Try fallback without authentication
202+ remoteLog .Printf ("gh CLI authentication failed, trying unauthenticated REST API fallback" )
203+ return resolveRefToSHAUnauthenticated (owner , repo , ref )
201204 }
202205 return "" , fmt .Errorf ("failed to resolve ref %s to SHA for %s/%s: %s: %w" , ref , owner , repo , strings .TrimSpace (outputStr ), err )
203206 }
@@ -236,7 +239,9 @@ func downloadFileFromGitHub(owner, repo, path, ref string) ([]byte, error) {
236239 // Check if this is an authentication error
237240 stderrStr := stderr .String ()
238241 if strings .Contains (stderrStr , "GH_TOKEN" ) || strings .Contains (stderrStr , "authentication" ) || strings .Contains (stderrStr , "not logged into" ) {
239- return nil , fmt .Errorf ("failed to fetch file content: GitHub authentication required. Please run 'gh auth login' or set GH_TOKEN/GITHUB_TOKEN environment variable: %w" , err )
242+ // Try fallback without authentication
243+ remoteLog .Printf ("gh CLI authentication failed, trying unauthenticated REST API fallback" )
244+ return downloadFileFromGitHubUnauthenticated (owner , repo , path , ref )
240245 }
241246 return nil , fmt .Errorf ("failed to fetch file content from %s/%s/%s@%s: %s: %w" , owner , repo , path , ref , strings .TrimSpace (stderrStr ), err )
242247 }
@@ -256,3 +261,109 @@ func downloadFileFromGitHub(owner, repo, path, ref string) ([]byte, error) {
256261
257262 return content , nil
258263}
264+
265+ // resolveRefToSHAUnauthenticated resolves a git ref to SHA using unauthenticated REST API
266+ // This is a fallback for when gh CLI authentication is not available
267+ func resolveRefToSHAUnauthenticated (owner , repo , ref string ) (string , error ) {
268+ remoteLog .Printf ("Attempting to resolve ref %s to SHA for %s/%s using unauthenticated API" , ref , owner , repo )
269+
270+ // Use curl to make unauthenticated request
271+ url := fmt .Sprintf ("https://api.github.com/repos/%s/%s/commits/%s" , owner , repo , ref )
272+ cmd := exec .Command ("curl" , "-s" , "-H" , "Accept: application/vnd.github.v3+json" , url )
273+
274+ output , err := cmd .CombinedOutput ()
275+ if err != nil {
276+ return "" , fmt .Errorf ("failed to resolve ref using unauthenticated API: %w" , err )
277+ }
278+
279+ // Parse JSON response
280+ var response struct {
281+ SHA string `json:"sha"`
282+ Message string `json:"message"`
283+ }
284+
285+ if err := json .Unmarshal (output , & response ); err != nil {
286+ return "" , fmt .Errorf ("failed to parse JSON response: %w" , err )
287+ }
288+
289+ // Check for error message in response
290+ if response .Message != "" {
291+ if strings .Contains (response .Message , "Not Found" ) {
292+ return "" , fmt .Errorf ("ref %s not found in %s/%s" , ref , owner , repo )
293+ }
294+ if strings .Contains (response .Message , "rate limit" ) {
295+ return "" , fmt .Errorf ("GitHub API rate limit exceeded" )
296+ }
297+ return "" , fmt .Errorf ("GitHub API error: %s" , response .Message )
298+ }
299+
300+ // Validate it's a valid SHA (40 hex characters)
301+ if len (response .SHA ) != 40 || ! isHexString (response .SHA ) {
302+ return "" , fmt .Errorf ("invalid SHA format returned: %s" , response .SHA )
303+ }
304+
305+ remoteLog .Printf ("Successfully resolved ref %s to SHA %s using unauthenticated API" , ref , response .SHA )
306+ return response .SHA , nil
307+ }
308+
309+ // downloadFileFromGitHubUnauthenticated downloads a file using unauthenticated REST API
310+ // This is a fallback for when gh CLI authentication is not available
311+ func downloadFileFromGitHubUnauthenticated (owner , repo , path , ref string ) ([]byte , error ) {
312+ remoteLog .Printf ("Attempting to download %s/%s/%s@%s using unauthenticated API" , owner , repo , path , ref )
313+
314+ // Use curl to make unauthenticated request
315+ url := fmt .Sprintf ("https://api.github.com/repos/%s/%s/contents/%s?ref=%s" , owner , repo , path , ref )
316+ cmd := exec .Command ("curl" , "-s" , "-H" , "Accept: application/vnd.github.v3+json" , url )
317+
318+ output , err := cmd .CombinedOutput ()
319+ if err != nil {
320+ return nil , fmt .Errorf ("failed to fetch file using unauthenticated API: %w" , err )
321+ }
322+
323+ // Parse JSON response
324+ var response struct {
325+ Content string `json:"content"`
326+ Encoding string `json:"encoding"`
327+ Message string `json:"message"`
328+ }
329+
330+ if err := json .Unmarshal (output , & response ); err != nil {
331+ return nil , fmt .Errorf ("failed to parse JSON response: %w" , err )
332+ }
333+
334+ // Check for error message in response
335+ if response .Message != "" {
336+ if strings .Contains (response .Message , "Not Found" ) {
337+ return nil , fmt .Errorf ("file %s not found in %s/%s@%s" , path , owner , repo , ref )
338+ }
339+ if strings .Contains (response .Message , "rate limit" ) {
340+ return nil , fmt .Errorf ("GitHub API rate limit exceeded" )
341+ }
342+ return nil , fmt .Errorf ("GitHub API error: %s" , response .Message )
343+ }
344+
345+ // Verify encoding
346+ if response .Encoding != "base64" {
347+ return nil , fmt .Errorf ("unexpected encoding: %s (expected base64)" , response .Encoding )
348+ }
349+
350+ // Remove newlines and whitespace from base64 content
351+ contentBase64 := strings .ReplaceAll (response .Content , "\n " , "" )
352+ contentBase64 = strings .ReplaceAll (contentBase64 , " " , "" )
353+ contentBase64 = strings .TrimSpace (contentBase64 )
354+
355+ if contentBase64 == "" {
356+ return nil , fmt .Errorf ("empty content returned from GitHub API" )
357+ }
358+
359+ // Decode base64 content
360+ decodeCmd := exec .Command ("base64" , "-d" )
361+ decodeCmd .Stdin = strings .NewReader (contentBase64 )
362+ content , err := decodeCmd .Output ()
363+ if err != nil {
364+ return nil , fmt .Errorf ("failed to decode base64 content: %w" , err )
365+ }
366+
367+ remoteLog .Printf ("Successfully downloaded %s/%s/%s@%s using unauthenticated API (%d bytes)" , owner , repo , path , ref , len (content ))
368+ return content , nil
369+ }
0 commit comments