A simple CLI to backport GitHub PRs to a target branch.
From the repo directory, install in editable mode so the backporter command is on your PATH:
pip install -e .Or use a venv: python3 -m venv .venv && .venv/bin/pip install -e .
Run from anywhere after installing:
backporter -t owner/repo:branch -p https://github.com/owner/repo/pull/123backporter -C ../releases/248 -p https://github.com/ClickHouse/ClickHouse/pull/72226 --make-description -t Altinity/ClickHouse:customizations/24.8.14| Argument | Meaning |
|---|---|
-C ../releases/248 |
Use this existing clone (no fresh clone). |
-p https://github.com/ClickHouse/ClickHouse/pull/72226 |
PR to backport (from upstream ClickHouse). |
--make-description |
Print changelog category + entry from the PR body to stdout (and append PR link + author to the entry). |
-t Altinity/ClickHouse:customizations/24.8.14 |
Target: push to repo Altinity/ClickHouse, branch customizations/24.8.14. Backport branch will be backports/customizations/24.8.14/72226. |
- Clones the target repo and checks out the target branch
- Creates a new branch named
<target_branch>/<PR_number>(e.g.main/42) - Cherry-picks the PR merge commit into this branch
- Pushes the branch to the target repo
- Python 3.10+
- Git
- PyGithub (installed automatically with
pip install -e .) - Network access to GitHub (HTTPS; use
git credentialfor private repos)
A GITHUB_TOKEN env var (or --token) is recommended for private repos and to avoid API rate limits.
| Option | Description |
|---|---|
-t, --target |
Target in format owner/repo:branch (required unless --make-description only or --conflicts-resolved) |
-p, --pr |
URL of the PR to backport (not needed with --conflicts-resolved) |
--make-description |
Output changelog description (category + entry) from the PR body to stdout |
--conflicts-resolved |
Finish backport after you resolved conflicts: git add ., cherry-pick --continue, push |
-C, --repo-dir |
Use existing cloned repo instead of cloning (avoids re-cloning large repos) |
--work-dir |
Directory to clone into when not using -C (default: temp dir, deleted after) |
--token |
GitHub token for API (default: GITHUB_TOKEN env var) |
For large repos, reuse your existing clone instead of re-cloning:
backporter -C /path/to/repo -t myorg/myrepo:main -p https://github.com/myorg/myrepo/pull/42The script will fetch the latest target branch, create the backport branch, cherry-pick, and push. Use a clean working tree.
If the backport branch already exists locally, the script prompts: Delete and recreate from clean target? [y/N]
- No (or Enter): aborts the backport; if
--make-descriptionwas given, only the changelog description is printed. - Yes: the existing branch is deleted and recreated from the current target branch, then the cherry-pick runs as usual.
If the repo is in a conflicted or dirty state (e.g. you left a cherry-pick with conflicts), git checkout to the target branch will fail. The script then prompts: Unresolved conflicts or dirty state. Recreate backport branch from clean target? [y/N]
- No: leaves the repo as is; if
--make-descriptionwas given, the changelog description is printed. - Yes: runs
git cherry-pick --abort, checks out the target branch, pulls, deletes the backport branch, then recreates it and runs the cherry-pick again.
If the cherry-pick hits merge conflicts, the script does not abort the cherry-pick: the branch is left with conflicts for you to resolve manually. The script prints the list of conflicted files and exits with code 1. If --make-description was given, the changelog description is still printed.
After resolving conflicts, you can either run git add <paths> and git cherry-pick --continue and push by hand, or use:
backporter --conflicts-resolved -C ../releases/248 -t Altinity/ClickHouse:customizations/24.8.14That runs git add ., git cherry-pick --continue (with no editor), and pushes the current branch to the target repo. No -p (PR URL) is needed.
To output the changelog description (Changelog category + Changelog entry) from a PR body to stdout:
backporter --make-description -p https://github.com/owner/repo/pull/123With --target, the description is printed after a successful backport. Without --target, only the description is printed (no clone/cherry-pick/push).
When your target repo is a fork of the base repo (where the PR was merged), the script adds the base repo as an upstream remote and fetches from it. Cherry-pick uses -m 1 for merge commits.
backporter -C /path/to/your/fork -t myorg/repo:main -p https://github.com/upstream/repo/pull/100