diff --git a/services/github_auth.go b/services/github_auth.go index 683463f..b5a5aac 100644 --- a/services/github_auth.go +++ b/services/github_auth.go @@ -287,6 +287,44 @@ func GetGraphQLClient() (*graphql.Client, error) { return client, nil } +// GetGraphQLClientForOrg returns a GitHub GraphQL API client authenticated for a specific organization +func GetGraphQLClientForOrg(org string) (*graphql.Client, error) { + // Check if we have a cached token for this org + if token, ok := installationTokenCache[org]; ok && token != "" { + client := graphql.NewClient("https://api.github.com/graphql", &http.Client{ + Transport: &transport{token: token}, + }) + return client, nil + } + + // Get installation ID for the organization + installationID, err := getInstallationIDForOrg(org) + if err != nil { + return nil, fmt.Errorf("failed to get installation ID for org %s: %w", org, err) + } + + // Get JWT token + token, err := getOrRefreshJWT() + if err != nil { + return nil, fmt.Errorf("failed to get JWT: %w", err) + } + + // Get installation access token + installationToken, err := getInstallationAccessToken(installationID, token, HTTPClient) + if err != nil { + return nil, fmt.Errorf("failed to get installation token for org %s: %w", org, err) + } + + // Cache the token + installationTokenCache[org] = installationToken + + // Create and return client + client := graphql.NewClient("https://api.github.com/graphql", &http.Client{ + Transport: &transport{token: installationToken}, + }) + return client, nil +} + // getOrRefreshJWT returns a valid JWT token, generating a new one if expired func getOrRefreshJWT() (string, error) { // Check if we have a valid cached JWT diff --git a/services/github_read.go b/services/github_read.go index e213712..716c575 100644 --- a/services/github_read.go +++ b/services/github_read.go @@ -26,9 +26,10 @@ func GetFilesChangedInPr(owner string, repo string, pr_number int) ([]ChangedFil } } - client, err := GetGraphQLClient() + // Use org-specific client to ensure we have the right installation token + client, err := GetGraphQLClientForOrg(owner) if err != nil { - return nil, fmt.Errorf("failed to get GraphQL client: %w", err) + return nil, fmt.Errorf("failed to get GraphQL client for org %s: %w", owner, err) } ctx := context.Background() diff --git a/services/github_write_to_target.go b/services/github_write_to_target.go index e77066e..79b2ef6 100644 --- a/services/github_write_to_target.go +++ b/services/github_write_to_target.go @@ -45,6 +45,25 @@ func normalizeRepoName(repoName string) string { return repoOwner() + "/" + repoName } +// normalizeRefPath ensures a ref path is in the correct format for different GitHub API calls. +// For GetRef: expects "heads/main" (no "refs/" prefix) +// For UpdateRef: expects "refs/heads/main" (full ref path) +func normalizeRefPath(branchPath string, fullPath bool) string { + // Strip "refs/" prefix if present + refPath := strings.TrimPrefix(branchPath, "refs/") + + // Ensure "heads/" prefix exists (unless it's a tag) + if !strings.HasPrefix(refPath, "heads/") && !strings.HasPrefix(refPath, "tags/") { + refPath = "heads/" + refPath + } + + // Add "refs/" prefix back if full path is needed + if fullPath { + return "refs/" + refPath + } + return refPath +} + // AddFilesToTargetRepoBranch uploads files to the target repository branch // using the specified commit strategy (direct or via pull request). func AddFilesToTargetRepoBranch() { @@ -344,8 +363,11 @@ func createCommitTree(ctx context.Context, client *github.Client, targetBranch U retryDelay := time.Duration(initialRetryDelay) * time.Millisecond + // GetRef expects "heads/main" format (no "refs/" prefix) + refPath := normalizeRefPath(targetBranch.BranchPath, false) + for attempt := 1; attempt <= maxRetries; attempt++ { - ref, _, err = client.Git.GetRef(ctx, owner, repoName, targetBranch.BranchPath) + ref, _, err = client.Git.GetRef(ctx, owner, repoName, refPath) if err == nil && ref != nil { break // Success } @@ -405,8 +427,10 @@ func createCommit(ctx context.Context, client *github.Client, targetBranch Uploa } // Update branch ref directly (no second GET) + // UpdateRef expects full ref path "refs/heads/main" + fullRefPath := normalizeRefPath(targetBranch.BranchPath, true) ref := &github.Reference{ - Ref: github.String(targetBranch.BranchPath), // e.g., "refs/heads/main" + Ref: github.String(fullRefPath), Object: &github.GitObject{SHA: github.String(newCommit.GetSHA())}, } if _, _, err := client.Git.UpdateRef(ctx, owner, repoName, ref, false); err != nil { diff --git a/services/webhook_handler_new.go b/services/webhook_handler_new.go index dd7ec04..a392677 100644 --- a/services/webhook_handler_new.go +++ b/services/webhook_handler_new.go @@ -45,7 +45,11 @@ func simpleVerifySignature(sigHeader string, body, secret []byte) bool { // RetrieveFileContentsWithConfigAndBranch fetches file contents from a specific branch func RetrieveFileContentsWithConfigAndBranch(ctx context.Context, filePath string, branch string, repoOwner string, repoName string) (*github.RepositoryContent, error) { - client := GetRestClient() + // Use org-specific client to ensure we have the right installation token + client, err := GetRestClientForOrg(repoOwner) + if err != nil { + return nil, fmt.Errorf("failed to get GitHub client for org %s: %w", repoOwner, err) + } fileContent, _, _, err := client.Repositories.GetContents( ctx,