Exploring GitHub Pull Requests Automation
As development teams grow and projects become more complex, automating processes becomes critical to maintaining productivity and code quality. I’m a big fan of using Azure Pipelines to automate builds, testing, and general code quality. However, recently, I’ve started exploring GitHub Workflows to automate managing pull requests (PR) to make it easier to identify what they relate to.
In this post, I want to share my experience setting up some initial workflows to handle tagging PRs. Whether you’re new to GitHub Actions or looking for ideas to enhance your current CI/CD pipeline, this guide offers practical insights and tips to help you get started.
Automating GitHub
The majority of my CI/CD pipeline experience has centred around Azure DevOps and Azure Pipeline which I’ve used extensively for CI/CD pipelines in many projects. As I’ve started to use GitHub more, I’ve wanted to experiment with the GitHub CLI to easily integrate with the GitHub API to automate processes.
gh pr view 1 --repo owner/scratchpad
As the example the above command will retrieve and display details about PR #1 in the specified repo.
gh pr view 1 --repo owner/scratchpad --json id,title,body
In addition, the --json
argument can be added to many commands to return a JSON object, allowing programmatic handling of the responses, such as using ConvertFrom-Json
in PowerShell.
Assigning Reviewers
The first workflow I wanted to create was to automate assigning a configurable list of reviewers to PRs.
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$PRNumber,
[Parameter(Mandatory = $true)]
[string[]]$Reviewers
)
begin
{
$InformationPreference = 'Continue'
if ($env:RUNNER_DEBUG)
{
$DebugPreference = 'Continue'
$VerbosePreference = 'Continue'
}
}
process
{
Write-Debug "prNumber=$PRNumber"
Write-Debug "reviewers=$($reviewers | ConvertTo-Json -Compress)"
Write-Debug "Getting PR"
$pr = gh pr view $prNumber --json author | ConvertFrom-Json
$author = $pr.author.login
Write-Verbose "author=$author"
Write-Debug "Filtering out author"
$reviewers = $reviewers | Where-Object { $_ -ine $author }
$reviewersStr = $reviewers | ConvertTo-Json -Compress
Write-Verbose "reviewersStr=$reviewersStr"
Write-Debug "Getting current repo"
$repo = gh repo view --json name,owner | ConvertFrom-Json
foreach ($r in $reviewers)
{
Write-Debug "Assigning reviewer $r"
gh api --method POST `
-H "Accept: application/vnd.github+json" `
-H "X-GitHub-Api-Version: 2022-11-28" `
/repos/$($repo.owner.login)/$($repo.name)/pulls/$prNumber/requested_reviewers `
-f "reviewers[]=$r" `
--silent
}
Write-Information "Assigned reviewers"
}
The scripts takes parameters for a PR number and GitHub usernames to be assigned as reviewers. Using the GitHub CLI, the author of the repo is evaluated and removed from the list of reviewers with the remaining reviewers added using the gh api
command.
N.B. The gh api
command is currently used to add the reviewers due to a bug in the handling of workflow permissions.
name: Assign Reviewers
on:
pull_request:
types:
- opened
- ready_for_review
- reopened
jobs:
assign-reviewers:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Print Environment Variables
shell: pwsh
run: |
Get-ChildItem Env: | ForEach-Object {Write-Host "$($_.Name)=$($_.Value)"}
- name: Assign Reviewers
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
run: |
./scripts/AssignReviewers.ps1 `
-PRNumber "${{ github.event.number }}" `
-Reviewers "github-user1", "github-user2"
The above workflow is triggered when a pull request is opened (including re-opened and published after drafting) and executes the script passing in values for PRNumber and Reviewers. To authenticate the CLI used in the script, the GH_TOKEN
environment variable is set using the $
workflow variable.
Adding the reviewers automatically results in greater awareness by triggering notifications and greater ownership of code reviews.
Tagging
When dealing with a large number of PRs, it’s important to be able to quickly and easily identify what it relates to. To help with this I developed a workflow to start tagging PRs to categorise them.
<!-- Provide the release in the format vx.x.x.x -->
**Release:**
<!-- Other PR template details ...-->
Firstly I created a pull request template like above to capture certain details when the pull request is raised. This template is then placed at .github\pull_request_template.md.
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string]$PRNumber,
[Parameter()]
[string[]]$SupportedBranchTypes = @("feature","hotfix","release")
)
begin {
$InformationPreference = 'Continue'
if ($env:RUNNER_DEBUG)
{
$DebugPreference = 'Continue'
$VerbosePreference = 'Continue'
}
}
process {
function LabelPR {
param (
[Parameter(Mandatory=$true)]
[string]$PRNumber,
[Parameter(Mandatory=$true)]
[string]$Label
)
process {
Write-Verbose "label=$Label"
Write-Debug "Getting labels"
$labels = gh label list --json name | ConvertFrom-Json | Select-Object -ExpandProperty name
Write-Debug "Check label exists"
$exists = $labels -contains $Label
if (-not $exists)
{
Write-Debug "Creating label"
gh label create $Label
}
Write-Information "Labelling PR with `"$Label`""
gh pr edit $PRNumber --add-label $Label | Out-Null
}
}
Write-Verbose "prNumber=$prNumber"
Write-Debug "Getting PR content"
$pr = gh pr view $prNumber --json id,body,state,baseRefName,headRefName,createdAt | ConvertFrom-Json
$regex = "(?<!<!--.*)(?<=\*{2}[Rr]elease:?\*{2}\s?)(v\d(\.\d){2,3})"
Write-Verbose "regex=$regex"
Write-Debug "Matching release number"
$match = $pr.body -match $regex
if ($match)
{
$releaseNumber = $Matches[0]
LabelPR -PRNumber $PRNumber -Label $releaseNumber
}
if ($SupportedBranchTypes)
{
Write-Debug "Splitting branch name"
$branchSplit = $pr.headRefName -split "/"
$branchType = $branchSplit[0].ToLower()
Write-Verbose "branchType=$branchType"
if ($branchType -in $SupportedBranchTypes)
{
LabelPR -PRNumber $PRNumber -Label $branchType
}
else
{
Write-Warning "Invalid branch type $branchType"
}
}
}
I’ve created a PowerShell script to process the pull request. There are a few key aspects to note:
- A private helper function
LabelPR
has been added to upsert labels - Regex is used to extract the value provided for the release in the pull request body and added as a label
- Branches conforming to the branchType/name format have the prefix extracted and added as a tag
name: Label PR
on:
pull_request:
types:
- opened
- edited
- ready_for_review
- reopened
jobs:
job:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Print Environment Variables
shell: pwsh
run: |
Get-ChildItem Env: | ForEach-Object {Write-Host "$($_.Name)=$($_.Value)"}
- name: Label PR
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
run: |
./scripts/LabelPR.ps1 `
-PRNumber "${{ github.event.number }}"
Like the previous automation, the PowerShell script is attached to a workflow with pull request triggers.
The resulting tags can then be used to distinguish features from hotfixes and which requests belong to version X or version Y allowing easier prioritisation of PRs so that more urgent requests are reviewed first. This automation could be expanded further by adding more details to the pull request template and then extracting these as tags.
Wrapping Up
Automation is a powerful tool for reducing repetitive tasks and improving workflow efficiency, and GitHub Workflows has proven to be a versatile platform for managing pull requests. By automating processes like assigning reviewers and tagging PRs, you can streamline collaboration, enhance visibility, and prioritise tasks more effectively.
The workflows in this post are just the beginning. GitHub Actions and the GitHub CLI offers a wealth of possibilities to extend automation even further and building custom scripts tailored to your team’s needs and processes. Whether you’re new to GitHub automation or already familiar with it, experimenting with workflows like these can open up new opportunities to enhance your development process.
I’ve thoroughly enjoyed exploring GitHub Workflows and the CLI and plan to continue experimenting. I hope you’ve enjoyed this post and if you’ve tried something similar or have comments, please feel free to share below.
Comments