Backport action
Fast and flexible GitHub action to backport merged pull requests to selected branches.
This can be useful when you're supporting multiple versions of your product. After fixing a bug, you may want to apply that patch to the other versions. The manual labor of cherry-picking the individual commits can be automated using this action.
Features
- Works out of the box - No configuration required / Defaults for everything
- Fast - Only fetches the bare minimum / Supports shallow clones
- Flexible - Supports all merge methods including merge queue and Bors
- Configurable - Use inputs and outputs to fit it to your project
- Transparent - Informs about its success / Cherry-picks with
-x
How it works
You can select the branches to backport merged pull requests in two ways:
- using labels on the merged pull request.
label_pattern input
- using the
target_branchesinput
- fetch and checkout a new branch from the target branch
- cherry-pick commits containing the merged pull request's changes, using the
cherry_pickinginput - create a pull request to merge the new branch into the target branch
- comment on the original pull request about its success
-x flag.
Usage
Add the following workflow configuration to your repository's .github/workflows folder.
name: Backport merged pull request
on:
pull_request_target:
types: [closed]
permissions:
contents: write # so it can comment
pull-requests: write # so it can create pull requests
jobs:
backport:
name: Backport pull request
runs-on: ubuntu-latest
# Only run on merged PRs with a backport label (default label_pattern)
if: >
github.event.pull_request.merged &&
contains(toJSON(github.event.pull_request.labels.*.name), '"backport ')
steps:
- uses: actions/checkout@v6
- name: Create backport pull requests
uses: korthout/backport-action@v4
Note
This workflow runs onpull_request_targetso thatGITHUB_TOKENhas write access to the repo when the merged pull request comes from a forked repository.
This write access is necessary for the action to push the commits it cherry-picked.
Trigger using a comment
You can also trigger the backport action by writing a comment containing /backport on a merged pull request.
To enable this, add the following workflow configuration to your repository's .github/workflows folder.
# Run on merged PRs with a backport label (default Trigger backport action using a comment
name: Backport merged pull request
on:
pull_request_target:
types: [closed]
issue_comment:
types: [created]
permissions:
contents: write # so it can comment
pull-requests: write # so it can create pull requests
jobs:
backport:
name: Backport pull request
runs-on: ubuntu-latest
label_pattern),
# or on /backport comments.
if: >
(
github.event_name == 'pull_request_target' &&
github.event.pull_request.merged &&
contains(toJSON(github.event.pull_request.labels.*.name), '"backport ')
) || (
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
startsWith(github.event.comment.body, '/backport')
)
steps:
- uses: actions/checkout@v6
- name: Create backport pull requests
uses: korthout/backport-action@v4
Determine target branches dynamically
When the default label_pattern can't express your targeting rule, compute target_branches at workflow runtime instead. The target might come from a reviewer's comment, the pull request's base branch, or a config file in the repo.
The example uses the comment case: someone writes backport:
needs: determine-target-branch
if: needs.determine-target-branch.outputs.branch != ''
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Create backport pull requests
uses: korthout/backport-action@v4
with:
target_branches: ${{ needs.determine-target-branch.outputs.branch }}Determine target branches dynamically from a comment
/backport on a merged pull request, and becomes the target. The first job parses the comment; the second runs the backport only when parsing yielded a non-empty branch. Replace the parse step with whatever computes the target for your repo.name: Backport via comment
on:
issue_comment:
types: [created]
permissions:
contents: write # so it can comment
pull-requests: write # so it can create pull requests
jobs:
determine-target-branch:
# Only run on PR comments starting with /backport (note the trailing space — an argument is required).
if: >
github.event.issue.pull_request &&
startsWith(github.event.comment.body, '/backport ')
runs-on: ubuntu-latest
outputs:
branch: ${{ steps.parse-comment.outputs.branch }}
steps:
- id: parse-comment
# Take the first line after /backport as the target branch.
# The comment body is passed via env to avoid script injection.
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
branch=$(printf '%s' "$COMMENT_BODY" | head -n1 | sed 's|^/backport ||')
echo "branch=${branch}" >> "$GITHUB_OUTPUT"
target_branches accepts a space-delimited list, so the same parsing approach extends to /backport branch-a branch-b if you want to allow multiple targets per comment.
Signing cherry-picked commits
You may want the cherry-picked commits to be signed, for example to satisfy a protected branch rule, an org policy, or the repo's existing convention.
By default, cherry-picked commits are committed by To sign them, add a step before the backport that configures git to sign commits (for example by importing a GPG key that enables Pin any third‑party action you use for this by commit SHA for supply‑chain security.Sign cherry-picked commits with GPG
github-actions[bot] and are not signed. The original author remains the author; only the committer changes.git_commit_gpgsign), then pass the matching committer name and email to the action so the signature lines up with the committer....
id: import-gpg
uses: crazy-max/[email protected] # Or any other action to set up GPG
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
git_config_global: true
git_user_signingkey: true
git_commit_gpgsign: true
uses: korthout/backport-action@v4
with:
git_committer_name: ${{ steps.import-gpg.outputs.name }}
git_committer_email: ${{ steps.import-gpg.outputs.email }}Note
Commits appear as "Partially verified" (not "Unverified") in the GitHub UI. This is a GitHub limitation: the original author's signature can't be preserved when re-committing. The cherry-pick uses
-x so the new commit references the original as an audit trail.
Inputs
The action can be configured with the following optional inputs:
add_author_as_assignee
Default: false (disabled)
Controls whether to set the author of the original pull request as an assignee on the backport pull request. By default, the original author is not made an assignee.
add_author_as_reviewer
Default: false (disabled)
Controls whether to request a review from the author of the original pull request on the backport pull request. By default, the original author is not requested for review.
add_labels
Default: '' (disabled)
The action will add these labels (comma-delimited) to the backport pull request. By default, no labels are added.
add_reviewers
Default: '' (disabled)
The action will add these reviewers (comma-delimited) to the backport pull request. By default, no reviewers are added.
add_team_reviewers
Default: '' (disabled)
The action will add these teams as reviewers (comma-delimited) to the backport pull request. By default, no teams are added.
auto_merge_enabled
Default: false (disabled)
Controls whether to auto-merge the created backport pull request.
When enabled, backport pull requests will automatically merge when all required checks pass and approvals are received.
Can be set to a simple boolean (true/false) or controlled dynamically via workflow expressions. Examples:
Simple boolean (always enable or disable):
with:
auto_merge_enabled: true
Opt-in with label (enable auto-merge only when backport-auto-merge label is present):
with:
auto_merge_enabled: ${{ contains(github.event.pull_request.labels.*.name, 'backport-auto-merge') }}
Opt-out with label (enable auto-merge by default, disable when backport-no-auto-merge label is present):
with:
auto_merge_enabled: ${{ !contains(github.event.pull_request.labels.*.name, 'backport-no-auto-merge') }}
By default, auto-merge is not enabled.
auto_merge_method
Default: merge
The merge method to use when auto-merge is enabled on backport PRs.
Valid options are:
merge- Create a merge commit (combines all commits with a merge commit)squash- Squash and merge (combines all commits into a single commit)rebase- Rebase and merge (replays commits individually without a merge commit)
By default, the backport PR is merged using a merge commit if auto-merge is enabled.
branch_name
Default: backport-${pull_number}-to-${target_branch}
Template used as the name for branches created by this action.
Placeholders can be used to define variable values.
These are indicated by a dollar sign and curly braces (${placeholder}).
Please refer to this action's README for all available placeholders.
cherry_picking
Default: auto
Determines which commits are cherry-picked.
When set to auto, the action cherry-picks the commits based on the method used to merge the pull request.
- For "Squash and merge", the action cherry-picks the squashed commit.
- For "Rebase and merge", the action cherry-picks the rebased commits.
- For "Merged as a merge commit", the action cherry-picks the commits from the pull request.
pull_request_head, the action cherry-picks the commits from the pull request.
Specifically, those reachable from the pull request's head and not reachable from the pull request's base.
By default, the action cherry-picks the commits based on the method used to merge the pull request.
cherry_picking_merge_mode
Default: default
Controls the cherry-pick merge mode. Accepted values:
default (standard cherry-pick behavior) or whitespace_tolerant
(ignores trailing whitespace and line-ending (CRLF vs LF) differences
when applying the cherry-pick). Use whitespace_tolerant when those
differences around the lines you're backporting cause cherry-pick
conflicts. Also use it when .gitattributes declares merge=union
for the file, where those same differences can silently duplicate
lines instead. Equivalent to git cherry-pick -Xignore-space-at-eol.
comment_style
Default: legacy
Controls the style of comments posted by the action.
When set to legacy, the action posts individual comments per target branch.
When set to summary, the action posts a single summary comment per workflow run with actionable details.
copy_all_reviewers
Default: false (disabled)
Controls whether to copy all reviewers from the original pull request to the backport pull request.
Note that this requests reviews from both requested reviewers, and people that have already reviewed the original pull request.
Use copy_requested_reviewers instead to only request reviews from requested reviewers.
By default, all reviewers are not copied.
copy_assignees
Default: false (disabled)
Controls whether to copy the assignees from the original pull request to the backport pull request. By default, the assignees are not copied.
copy_labels_pattern
Default: '' (disabled)
Regex pattern to match github labels which will be copied from the original pull request to the backport pull request.
Note that labels matching label_pattern are excluded.
By default, no labels are copied.
copy_milestone
Default: false (disabled)
Controls whether to copy the milestone from the original pull request to the backport pull request. By default, the milestone is not copied.
copy_requested_reviewers
Default: false (disabled)
Controls whether to copy the requested reviewers from the original pull request to the backport pull request.
Note that this does not request reviews from those users who already reviewed the original pull request.
Use copy_all_reviewers instead to also request reviews from those users.
By default, the requested reviewers are not copied.
experimental
Default:
{
"detect_merge_method": false
}
Configure experimental features by passing a JSON object. The following properties can be specified:
conflict_resolution
Default: fail
Specifies how the action will handle a conflict occuring during the cherry-pick. In all cases, the action will stop the cherry-pick at the first conflict encountered.
Behavior is defined by the option selected.
- When set to
failthe backport fails when the cherry-pick encounters a conflict. - When set to
draft_commit_conflictsthe backport will always create a draft pull request with the first conflict encountered committed.
downstream_repo
Define if you want to backport to a repository other than where the workflow runs.
By default, the action always backports to the repository in which the workflow runs.
downstream_owner
Define if you want to backport to another owner than the owner of the repository the workflow runs on.
Only takes effect if the downstream_repo property is also defined.
By default, uses the owner of the repository in which the workflow runs.
github_token
Default: ${{ github.token }}
Token to authenticate requests to GitHub. Used to create and label pull requests and to comment.
Either GITHUB_TOKEN or a repo-scoped Personal Access Token (PAT).
github_workspace
Default: ${{ github.workspace }}
Working directory for the backport action.
git_committer_name
Default: github-actions[bot]
Name of the committer for the cherry-picked commit.
git_committer_email
Default: github-actions[bot]@users.noreply.github.com
Email of the committer for the cherry-picked commit.
label_pattern
Default: ^backport ([^ ]+)$ (e.g. matches backport release-3.4)
Regex pattern to match the backport labels on the merged pull request.
Must contain a capture group for the target branch.
Label matching can be disabled entirely using an empty string '' as pattern.
The action will backport the pull request to each matched target branch. Note that the pull request's headref is excluded automatically. See How it works.
merge_commits
Default: fail
Specifies how the action should deal with merge commits on the merged pull request.
- When set to
failthe backport fails when the action detects one or more merge commits. - When set to
skipthe action only cherry-picks non-merge commits, i.e. it ignores merge commits.
pull_description
Default:
# Description
Backport of #${pull_number} to ${target_branch}.
Template used as description (i.e. body) in the pull requests created by this action.
Placeholders can be used to define variable values.
These are indicated by a dollar sign and curly braces (${placeholder}).
Please refer to this action's README for all available placeholders.
pull_title
Default: [Backport ${target_branch}] ${pull_title}
Template used as the title in the pull requests created by this action.
Placeholders can be used to define variable values.
These are indicated by a dollar sign and curly braces (${placeholder}).
Please refer to this action's README for all available placeholders.
source_pr_number
Default: '' (not set)
Specifies the pull request (by its number) to backport, i.e. the source pull request. When set, the action will backport the specified pull request to each target branch. When not set, the action determines the source pull request from the event payload.
target_branches
Default: '' (disabled)
The action will backport the pull request to each specified target branch (space-delimited). Note that the pull request's headref is excluded automatically. See How it works.
Can be used on its own (no labels required) or alongside backport labels — when both are set, the action backports to the union of the two. By default, only backport labels are used to specify the target branches.
See also Determine target branches dynamically for computing this input at workflow runtime.
Placeholders
In thepull_description and pull_title inputs, placeholders can be used to define variable values.
These are indicated by a dollar sign and curly braces (${placeholder}).
The following placeholders are available and are replaced with:
Placeholder | Replaced with
------------|------------
issue_refs | GitHub issue references to all issues mentioned in the original pull request description seperated by a space, e.g. #123 #456 korthout/backport-action#789
pull_author | The username of the original pull request's author, e.g. korthout
pull_description| The description (i.e. body) of the original pull request that is backported, e.g. Summary: This patch was created to..
pull_number | The number of the original pull request that is backported, e.g. 123
pull_title | The title of the original pull request that is backported, e.g. fix: some error
target_branch| The branchname to which the pull request is backported, e.g. release-0.23
Outputs
The action provides the following outputs:
Output | Description
-------|------------
created_pull_numbers | Space-separated list containing the identifying number of each created pull request. Or empty when the action created no pull requests. For example, 123 or 123 124 125.
was_successful | Whether or not the changes could be backported successfully to all targets. Either true or false.
was_successful_by_target | Whether or not the changes could be backported successfully to all targets - broken down by target. Follows the pattern {{label}}=true\|false.