Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 67 additions & 7 deletions src/Action/Issue.psm1
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
Join-Path $PSScriptRoot '..\Helpers.psm1' | Import-Module
Join-Path $PSScriptRoot '..\Github.psm1' | Import-Module
Join-Path $PSScriptRoot 'Issue' | Get-ChildItem -Filter '*.psm1' | Select-Object -ExpandProperty Fullname | Import-Module



function Test-Hash {
param (
[Parameter(Mandatory = $true)]
Expand Down Expand Up @@ -63,13 +66,63 @@ function Test-Hash {
} else {
Write-Log 'Hash mismatch confirmed.'

$masterBranch = ((Invoke-GithubRequest "repos/$REPOSITORY").Content | ConvertFrom-Json).default_branch
$message = @('You are right. Thank you for reporting.')
# Use GraphQL to fetch repository info and PRs in parallel
$owner, $repo = $REPOSITORY -split '/'
$graphqlQuery = @"
query(`$owner:String!, `$repo:String!) {
repository(owner:`$owner, name:`$repo) {
defaultBranchRef {
name
}
pullRequests(states:OPEN, first:100, orderBy:{field:UPDATED_AT, direction:DESC}) {
nodes {
number
title
body
baseRefName
}
}
}
rateLimit {
remaining
}
}
"@
Comment thread
coderabbitai[bot] marked this conversation as resolved.

$masterBranch = $null
$prs = $null

try {
Write-Log 'Attempting GraphQL query for repository and PRs...'
$response = Invoke-GithubGraphQL -Query $graphqlQuery -Variables @{
owner = $owner
repo = $repo
}

$content = $response.Content | ConvertFrom-Json
if ($content.data -and $content.data.repository) {
$masterBranch = $content.data.repository.defaultBranchRef.name
$prs = $content.data.repository.pullRequests.nodes
Write-Log "GraphQL query succeeded. Remaining rate limit: $($content.data.rateLimit.remaining)"
} else {
throw 'GraphQL returned no data'
}
} catch {
Write-Log "GraphQL query failed, falling back to REST API: $($_.Exception.Message)"
# Fallback to REST API
$masterBranch = ((Invoke-GithubRequest "repos/$REPOSITORY").Content | ConvertFrom-Json).default_branch
$prs = (Invoke-GithubRequest "repos/$REPOSITORY/pulls?state=open&base=$masterBranch&sort=updated").Content | ConvertFrom-Json
}

$message = @('You are right. Thank you for reporting.')
# TODO: Post labels at the end of function
Add-Label -ID $IssueID -Label 'verified', 'hash-fix-needed'
$prs = (Invoke-GithubRequest "repos/$REPOSITORY/pulls?state=open&base=$masterBranch&sorting=updated").Content | ConvertFrom-Json
$titleToBePosted = "$manifestNameAsInBucket@$($man.version): Fix hash"
$prs = $prs | Where-Object { $_.title -eq $titleToBePosted }
$prs = $prs | Where-Object {
$_.title -eq $titleToBePosted -and
(($_.baseRefName -eq $masterBranch) -or ($_.base.ref -eq $masterBranch))
}


# There is alreay PR for
if ($prs.Count -gt 0) {
Expand All @@ -88,9 +141,16 @@ function Test-Hash {
Invoke-GithubRequest "repos/$REPOSITORY/pulls/$prID" -Method Patch -Body @{ 'body' = (@("- Closes #$IssueID", $pr.body) -join "`r`n") }
Add-Label -ID $IssueID -Label 'duplicate'
} else {
# Check if default branch is protected
if (((Invoke-GithubRequest "repos/$REPOSITORY/branches/$masterBranch").Content | ConvertFrom-Json).protected) {
Write-Log 'PR - Create new branch and post PR'
# Check if default branch is protected (need REST API for this as GraphQL doesn't expose it)
$isProtected = $false
try {
$isProtected = ((Invoke-GithubRequest "repos/$REPOSITORY/branches/$masterBranch").Content | ConvertFrom-Json).protected
} catch {
Write-Log "Failed to check branch protection status: $($_.Exception.Message). Assuming branch is protected for safety."
$isProtected = $true
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

if ($isProtected) {

$branch = "$manifestNameAsInBucket-hash-fix-$(Get-Random -Maximum 258258258)"

Expand Down
195 changes: 193 additions & 2 deletions src/Github.psm1
Original file line number Diff line number Diff line change
@@ -1,5 +1,196 @@
Join-Path $PSScriptRoot 'Helpers.psm1' | Import-Module

function Invoke-GithubGraphQL {
<#
.SYNOPSIS
Invoke authenticated GitHub GraphQL API request with retry logic.
.PARAMETER Query
GraphQL query string.
.PARAMETER Variables
Hashtable of variables for the GraphQL query.
.PARAMETER MaxRetries
Maximum number of retry attempts on rate limit. Default is 3.
.EXAMPLE
Invoke-GithubGraphQL -Query 'query { viewer { login } }'
#>
param(
[Parameter(Mandatory)]
[String] $Query,
[Hashtable] $Variables,
[Int] $MaxRetries = 3
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

$graphqlUrl = 'https://api.github.com/graphql'
$retryCount = 0
$success = $false
$response = $null

while ($retryCount -lt $MaxRetries -and -not $success) {
try {
$body = @{
'query' = $Query
}
if ($Variables) {
$body['variables'] = $Variables
}

$parameters = @{
'Headers' = @{
'Authorization' = "Bearer $env:GITHUB_TOKEN"
'Accept' = 'application/json'
}
'Method' = 'Post'
'Uri' = $graphqlUrl
'Body' = (ConvertTo-Json $body -Depth 10)
'ContentType' = 'application/json'
}

Write-Log 'GraphQL Request' $parameters.Uri
Write-Log 'GraphQL Query' $Query

$response = Invoke-WebRequest @parameters
$content = $response.Content | ConvertFrom-Json

if ($content.errors) {
Write-Log 'GraphQL Errors' ($content.errors | ConvertTo-Json -Depth 10)

# Check for rate limit errors
$isRateLimit = $content.errors | Where-Object { $_.type -eq 'RATE_LIMITED' }
if ($isRateLimit) {
$retryCount++
if ($retryCount -lt $MaxRetries) {
$waitTime = [Math]::Pow(2, $retryCount) * 3
Write-Log "Rate limit hit, waiting $waitTime seconds before retry ($retryCount/$MaxRetries)"
Start-Sleep -Seconds $waitTime
continue
} else {
throw "Rate limit exceeded after $MaxRetries retries"
}
}

throw "GraphQL query failed: $($content.errors[0].message)"
}

$env:GH_REQUEST_COUNTER = ([int] $env:GH_REQUEST_COUNTER) + 1
$success = $true

return $response
} catch {
Write-Log "GraphQL request failed: $($_.Exception.Message)"

$retryCount++
if ($retryCount -lt $MaxRetries -and $_.Exception.Message -match 'rate limit|timeout|temporary') {
$waitTime = [Math]::Pow(2, $retryCount) * 3
Write-Log "Retrying in $waitTime seconds..."
Start-Sleep -Seconds $waitTime
} else {
throw
}
}
}

if (-not $success) {
throw "GraphQL request failed after $MaxRetries retries: $($response | Out-String)"
}
}

function Invoke-GithubGraphQLParallel {
<#
.SYNOPSIS
Execute multiple GraphQL queries in parallel to avoid rate limits.
.PARAMETER Queries
Array of hashtables containing Query and Variables.
.EXAMPLE
$queries = @(
@{ Query = 'query($owner:String!, $name:String!) { repository(owner:$owner, name:$name) { defaultBranch } }'; Variables = @{ owner = 'octocat'; name = 'Hello-World' } }
)
Invoke-GithubGraphQLParallel -Queries $queries
#>
param(
[AllowEmptyCollection()]
[Hashtable[]] $Queries
)

# Early return for empty or null queries
if ($null -eq $Queries -or $Queries.Count -eq 0) {
return @{ Results = @(); Errors = @(); FallbackUsed = $false }
}

$results = @()
$errors = @()
$runspacePool = [runspacefactory]::CreateRunspacePool(1, [Math]::Min(5, $Queries.Count))
$runspacePool.Open()
Comment thread
coderabbitai[bot] marked this conversation as resolved.

$jobs = @()
foreach ($q in $Queries) {
$powershell = [powershell]::Create()
$powershell.RunspacePool = $runspacePool
$powershell.AddScript({
param($Query, $Variables, $Token)
$env:GITHUB_TOKEN = $Token
$body = @{ 'query' = $Query }
if ($Variables) { $body['variables'] = $Variables }

$parameters = @{
'Headers' = @{ 'Authorization' = "Bearer $Token" }
'Method' = 'Post'
'Uri' = 'https://api.github.com/graphql'
'Body' = (ConvertTo-Json $body -Depth 10)
'ContentType' = 'application/json'
}

try {
$response = Invoke-WebRequest @parameters
$payload = $response.Content | ConvertFrom-Json
if ($payload.errors) {
return @{ Success = $false; Error = ($payload.errors | ConvertTo-Json -Depth 10) }
}
return @{ Success = $true; Data = $response }
} catch {
return @{ Success = $false; Error = $_.Exception.Message }
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}).AddArgument($q.Query).AddArgument($q.Variables).AddArgument($env:GITHUB_TOKEN) > $null

$jobs += @{
PowerShell = $powershell
AsyncResult = $powershell.BeginInvoke()
Query = $q
}
}

$successfulCount = 0
try {
foreach ($job in $jobs) {
try {
$result = $job.PowerShell.EndInvoke($job.AsyncResult)
if ($result.Success) {
$results += $result.Data
$successfulCount++
} else {
Write-Log "Parallel query failed: $($result.Error)"
$errors += @{ Query = $job.Query; Error = $result.Error }
}
} finally {
$job.PowerShell.Dispose()
}
}

# Update parent process counter after all runspaces complete
$env:GH_REQUEST_COUNTER = ([int]$env:GH_REQUEST_COUNTER) + $successfulCount
} finally {
$runspacePool.Close()
$runspacePool.Dispose()
}


if ($errors.Count -gt 0) {
Write-Log "Some GraphQL queries failed with errors: $($errors.Count)"
}

return @{ Results = $results; Errors = $errors; FallbackUsed = $false }
}


function Invoke-GithubRequest {
<#
.SYNOPSIS
Expand All @@ -14,7 +205,7 @@ function Invoke-GithubRequest {
Invoke-GithubRequest 'repos/User/Repo/pulls' -Method 'Post' -Body @{ 'body' = 'body' }
#>
param(
[Parameter(Mandatory, ValueFromPipeline)]
[Parameter(Mandatory)]
[String] $Query,
[Microsoft.PowerShell.Commands.WebRequestMethod] $Method = 'Get',
[Hashtable] $Body
Expand Down Expand Up @@ -313,5 +504,5 @@ function Get-LogURL {
return $logURL
}

Export-ModuleMember -Function Invoke-GithubRequest, Add-Comment, Get-AllChangedFilesInPR, New-Issue, Close-Issue, `
Export-ModuleMember -Function Invoke-GithubRequest, Invoke-GithubGraphQL, Invoke-GithubGraphQLParallel, Add-Comment, Get-AllChangedFilesInPR, New-Issue, Close-Issue, `
Add-Label, Remove-Label, Get-RateLimit, Get-JobID, Get-LogURL