1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
|
[CmdletBinding(SupportsShouldProcess)]
param(
# Please use the RepoOwner/RepoName format: e.g. Azure/azure-sdk-for-java
$RepoId,
# CentralRepoId the original PR to generate sync PR. E.g Azure/azure-sdk-tools for eng/common
$CentralRepoId,
# We start from the sync PRs, use the branch name to get the PR number of central repo. E.g. sync-eng/common-(<branchName>)-(<PrNumber>). Have group name on PR number.
# For sync-eng/common work, we use regex as "^sync-eng/common.*-(?<PrNumber>\d+).*$".
# For sync-.github/workflows work, we use regex as "^sync-.github/workflows.*-(?<PrNumber>\d+).*$".
$BranchRegex,
# Date format: e.g. Tuesday, April 12, 2022 1:36:02 PM. Allow to use other date format.
[AllowNull()]
[DateTime]$LastCommitOlderThan,
[Switch]$DeleteBranchesEvenIfThereIsOpenPR = $false,
$AuthToken
)
Set-StrictMode -version 3
. (Join-Path $PSScriptRoot common.ps1)
function Get-AllBranchesAndPullRequestInfo($owner, $repo) {
$query = @'
query($owner: String!, $repo: String!, $refPrefix: String!$endCursor: String) {
repository(owner: $owner, name: $repo) {
refs(first: 100, refPrefix: $refPrefix, after: $endCursor) {
nodes {
name
target {
commitUrl
... on Commit {
committedDate
}
}
associatedPullRequests(first: 100) {
nodes {
url
closed
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
'@
$all_branches = gh api graphql --paginate -F owner=$owner -F repo=$repo -F refPrefix='refs/heads/' -f query=$query `
--jq '.data.repository.refs.nodes[] | { name, commitUrl: .target.commitUrl, committedDate: .target.committedDate, pullRequests: .associatedPullRequests.nodes }' | ConvertFrom-Json
if ($LASTEXITCODE) {
LogError "Failed to retrieve branches for '$owner' and '$repo' running query '$query'"
exit $LASTEXITCODE
}
return $all_branches
}
LogDebug "Operating on Repo '$RepoId'"
# Setup GH_TOKEN for the gh cli commands
if ($AuthToken) {
$env:GH_TOKEN = $AuthToken
}
$owner, $repo = $RepoId -split "/"
# These will always be output at the end of the script. Their only purpose is for information gathering
# Total number returned from query
$totalBranchesFromQuery = 0
# reasons why a branch was skipped
$skippedBranchNotMatchRegex = 0
$skippedForCommitDate = 0
$skippedForOpenPRs = 0
$skippedForPRNotInBranch = 0
$skippedForPRNotInRepo = 0
# gh call counters
$ghPRViewCalls = 0
$ghBranchDeleteCalls = 0
try {
# Output the core rate limit at the start of processing. There's no real need
# to output this at the end because the GH call counts are being output
$coreRateLimit = Get-RateLimit core
Write-RateLimit $coreRateLimit
# Output the GraphQL rate limit before and after the call
$graphqlRateLimit = Get-RateLimit graphql
Write-RateLimit $graphqlRateLimit "Before GraphQL Call"
$branches = Get-AllBranchesAndPullRequestInfo $owner $repo
$graphqlRateLimit = Get-RateLimit graphql
Write-RateLimit $graphqlRateLimit "After GraphQL Call"
if ($branches) {
$totalBranchesFromQuery = $branches.Count
}
foreach ($branch in $branches)
{
$branchName = $branch.Name
if ($branchName -notmatch $BranchRegex) {
$skippedBranchNotMatchRegex++
continue
}
$openPullRequests = @($branch.pullRequests | Where-Object { !$_.Closed })
# If we have a central PR that created this branch still open don't delete the branch
if ($CentralRepoId)
{
$pullRequestNumber = $matches["PrNumber"]
# If central PR number is not found, then skip
if (!$pullRequestNumber) {
LogError "No PR number found in the branch name. Please check the branch name '$branchName'. Skipping..."
$skippedForPRNotInBranch++
continue
}
$ghPRViewCalls++
$centralPR = gh pr view --json 'url,closed' --repo $CentralRepoId $pullRequestNumber | ConvertFrom-Json
if ($LASTEXITCODE) {
LogError "PR '$pullRequestNumber' not found in repo '$CentralRepoId'. Skipping..."
$skippedForPRNotInRepo++
continue
} else {
LogDebug "Found central PR $($centralPR.url) and Closed=$($centralPR.closed)"
if (!$centralPR.Closed) {
$skippedForOpenPRs++
# Skipping if there is an open central PR open for the branch.
LogDebug "Central PR is still open so skipping the deletion of branch '$branchName'. Skipping..."
continue
}
}
}
else {
# Not CentralRepoId - not associated with a central repo PR
if ($openPullRequests.Count -gt 0 -and !$DeleteBranchesEvenIfThereIsOpenPR) {
$skippedForOpenPRs++
LogDebug "Found open PRs associate with branch '$branchName'. Skipping..."
continue
}
}
# If there is date filter, then check if branch last commit is older than the date.
if ($LastCommitOlderThan)
{
$commitDate = $branch.committedDate
if ($commitDate -gt $LastCommitOlderThan) {
$skippedForCommitDate++
LogDebug "The branch $branch last commit date '$commitDate' is newer than the date '$LastCommitOlderThan'. Skipping..."
continue
}
}
foreach ($openPullRequest in $openPullRequests) {
LogDebug "Note: Open pull Request '$($openPullRequest.url)' will be closed after branch deletion, given the central PR is closed."
}
$commitUrl = $branch.commitUrl
if ($PSCmdlet.ShouldProcess("'$branchName' in '$RepoId'", "Deleting branch on cleanup script")) {
$ghBranchDeleteCalls++
gh api "repos/${RepoId}/git/refs/heads/${branchName}" -X DELETE
if ($LASTEXITCODE) {
LogError "Deletion of branch '$branchName` failed, see command output above"
exit $LASTEXITCODE
}
LogDebug "The branch '$branchName' at commit '$commitUrl' in '$RepoId' has been deleted."
}
}
}
finally {
Write-Host "Number of branches returned from graphql query: $totalBranchesFromQuery"
# The $BranchRegex seems to be always set
if ($BranchRegex) {
Write-Host "Number of branches that didn't match the BranchRegex: $skippedBranchNotMatchRegex"
}
Write-Host "Number of branches skipped for newer last commit date: $skippedForCommitDate"
Write-Host "Number of branches skipped for open PRs: $skippedForOpenPRs"
Write-Host "Number of gh api calls to delete branches: $ghBranchDeleteCalls"
# The following are only applicable when $CentralRepoId is passed in
if ($CentralRepoId) {
Write-Host "The following are applicable because CentralRepoId was passed in:"
Write-Host " Number of gh pr view calls: $ghPRViewCalls"
Write-Host " Number of branches skipped due to PR not in the repository: $skippedForPRNotInRepo "
Write-Host " Number of branches skipped due to PR not in the branch name: $skippedForPRNotInBranch"
}
}
|