Create or Update PR Action
Following on from our theme of actions to commit and push changes, today’s action can commit and push any local edits and raise a pull request automatically.
Fact Sheet | |
---|---|
Author | gr2m |
Contributors | 8 |
Stars | 31 |
Repo | https://github.com/gr2m/create-or-update-pull-request-action |
Marketplace | https://github.com/marketplace/actions/create-or-update-pull-request |
What does it do?
A GitHub Action to create or update a pull request based on local changes
You can use the action to add, commit and push changes to a remote branch and create a pull request for those changes automatically. It can add all your files in a single commit, add multiple commits to a single PR and automatically add labels and assignees to any pull requests that it raises.
Oh, and it’s built by Gregor which means I instantly trust it.
How does it work?
- If there any uncommitted changes, files matching the pattern provided in the
path
input supplied are added and committed. You can customise the commit with thecommit-message
andauthor
inputs - If there are any local commits (created by step 1, or by any other means) the action pushes changes to a remote branch with the name specified in the
branch
input - Search for any open pull requests with
branch
as the HEAD commit using the search queryhead:${inputs.branch} type:pr is:open repo:${process.env.GITHUB_REPOSITORY}
- If there is an existing pull request, the action stops execution. Otherwise, a new pull request is created with the
title
andbody
inputs provided. Anylabels
andassignees
provided as inputs are automatically added
Using the search API to check if there is already an open pull request for a branch is a fantastic solution. When I needed to do the same, I uses the pulls.list
endpoint and passed in the head
parameter like so:
js
let pr = (await tools.github.pulls.list({owner,repo,head: `${owner}:${automationBranchName}`,})).data[0];if (!pr) {// Create a PR}
What I find interesting about this action is that it only uses the GitHub API for actions that it cannot perform using the git
binary. There is a runShellCommand
function that is used to run git fetch
, git commit
etc on the runner directly. When automating updates I’ve used the GitHub API to get and modify file contents to remove the requirement to use actions/checkout
as part of your workflow, but in this context you’re likely to have a workspace so it makes sense to interact directly with the files.
Another interesting choice is that the action does not configure a remote or add authentication details, instead opting to specify both values directly in the git
commit like so:
js
await runShellCommand(`git push -f https://x-access-token:${process.env.GITHUB_TOKEN}@github.com/${process.env.GITHUB_REPOSITORY}.git HEAD:refs/heads/${inputs.branch}`);
There’s nothing in the commit history to explain why, but if I had to hazard a guess it’s due to the fact that other processes can interact with git
config files, so by passing the details directly to the command it reduces the chances of anything interfering.
Finally, this action provides an example of how to provide lists of values in inputs
. The action uses a comma-separated list of values for labels
and assignees
to provide multiple values. There still isn’t consensus how to provide multiple values (some actions provide a JSON list and deserialise), but this is another vote for the “comma-separated” camp.
Common use cases
This action is most useful when you’ve got a task running on a schedule that fetches and commits updated data to the repo. If there are no changes, no commit or pull request will be created.
The README itself shows that it’s being used to keep track of Chinese Starbucks stores, download JSON schema updates periodically and even by the Node project itself to keep the license file up to date.
If you’re interested in learning more about this pattern, there’s a great post by Simon Willison on git scraping
as a concept.