patchsplit
Language: English | Simplified Chinese
Download GitHub pull requests, GitLab merge requests, or individual commits as
patches, one file per commit or one combined diff.
patchsplit is a command-line tool written in Rust. It fetches patches directly
from GitHub or GitLab without cloning the repository, making it useful for
reviewing, sharing, and applying changes locally.
- Per-commit patches: Keep the original patch content, commit messages, and
authorship, with numbered filenames in commit order.
- Single commit: Use
--commit to download one commit's .patch
with either a short or full hash.
- GitLab support: Pass
--gitlab to fetch merge requests and commits from
gitlab.com, including projects nested under subgroups.
- Tor support: Pass
--tor to download through the Tor network over a local
SOCKS5 proxy, resolving hostnames inside Tor.
- Combined diff: Use
--squash to export the request's net changes as a single patch.
- Predictable output: Choose an output directory; existing files are only
overwritten when you pass
--force.
- Localized CLI: English and built-in Simplified Chinese messages.
Installation | Usage | Localization |
Development | Contributing | License
Installation
patchsplit calls the system curl command to download patches. Make sure
curl is available in your PATH; the Debian/Ubuntu package declares it as a
dependency. Rust is only needed when building from source.
Ubuntu (PPA)
Add the project PPA and refresh the package index:
sudo add-apt-repository ppa:zitzhen/patchsplit
sudo apt update
Install patchsplit:
sudo apt install patchsplit
Debian / Ubuntu (.deb)
Download the .deb package from GitHub Releases,
then install it with the command below. Replace `` with the version in
the downloaded filename.
sudo apt install ./patchsplit__amd64.deb
Fedora / RHEL and compatible distributions (.rpm)
Download the main .rpm package from GitHub Releases.
Use its exact filename in place of ``:
sudo dnf install ./.rpm
Choose patchsplit--.*.x86_64.rpm, excluding any package with
debuginfo or debugsource in its name.
Arch Linux (AUR)
The community-maintained patchsplit-bin
package is provided by lingbopro.
Install it with either AUR helper:
paru -S patchsplit-bin
# Or:
yay -S patchsplit-bin
Linux / macOS (prebuilt binaries)
Download the archive for your platform from
GitHub Releases.
The release workflow builds x86_64 binaries for Linux, macOS, and Windows.
Extract the Linux archive:
tar -xzf patchsplit-linux-x86_64.tar.gz
Or extract the macOS archive:
tar -xzf patchsplit-macos-x86_64.tar.gz
Then install the extracted binary:
sudo install -m 755 patchsplit /usr/local/bin/patchsplit
patchsplit --version
If macOS blocks a binary you have verified and trust, remove its quarantine
attribute:
xattr -d com.apple.quarantine /usr/local/bin/patchsplit
Windows
Download patchsplit-windows-x86_64.zip from
GitHub Releases, then extract
and run it in PowerShell:
Expand-Archive .\patchsplit-windows-x86_64.zip -DestinationPath .\patchsplit
.\patchsplit\patchsplit.exe --version
To use it globally, add the extracted patchsplit directory to your user Path
environment variable. The CLI needs curl.exe available in Path.
From source
With Rust, Cargo, and Git installed:
git clone https://github.com/zitzhen/patchsplit.git
cd patchsplit
cargo install --path . --locked
patchsplit --version
Ensure Cargo's binary directory (usually ~/.cargo/bin) is in your PATH.
Usage
patchsplit [--out ] [--force] [--squash]
patchsplit [--out ] [--force] [--squash]
patchsplit --commit [--out ] [--force]
patchsplit --commit [--out ] [--force]
patchsplit --gitlab [--out ] [--force] [--squash]
patchsplit --gitlab --commit [--out ] [--force]
Split a pull request by commit
Replace the repository and PR number with the pull request you want to download:
patchsplit rust-lang/rust 12345
patchsplit openai codex 42 -o pr-42-patches
The default output directory is patches/, created automatically if needed.
Filenames use a zero-padded index and a sanitized commit subject, for example:
patches/
0001-add-parser.patch
0002-wire-cli.patch
Existing output files are not overwritten by default. Pass --force to replace
them.
The per-commit files preserve GitHub's mail-formatted patches. To apply them
with commit messages and authorship, run the following inside the target Git
repository, checked out at a compatible base:
git am /path/to/patches/*.patch
Export a combined diff
patchsplit openai/codex 42 --squash -o pr-42-patches
-s, --squash downloads GitHub's aggregate PR .diff and writes
pr-.patch. It represents the net change from the PR's merge base
to its head: repeated edits are combined and reverted changes disappear,
rather than concatenating the per-commit patches.
Apply it from inside the target repository on the corresponding base:
git apply --check /path/to/pr-42-patches/pr-42.patch
git apply /path/to/pr-42-patches/pr-42.patch
| Mode | Output | Commit messages and authorship | Apply with |
|---|
| Default (GitHub PR) | One numbered patch per commit | Preserved | git am |
--squash (GitHub PR) | One pr-.patch | Not included | git apply |
--commit (GitHub) | One .patch | Preserved | git am |
--gitlab (GitLab MR) | One numbered patch per commit | Preserved | git am |
--gitlab --squash | One mr-.patch | Not included | git apply |
--gitlab --commit | One .patch | Preserved | git am |
The combined output is a raw diff, not a git am mailbox. An empty net diff
is reported as an error and no file is written. Binary changes are limited to
the data GitHub includes in its diff; binary file contents may not be included.
Download a single commit
Pass --commit (or -commit ) with a short or full commit hash
instead of a pull request number:
patchsplit zitzhen patchsplit -commit b430113
patchsplit zitzhen/patchsplit --commit b4301133226e5c3a464cff9649de0b321c0b0a2e
This fetches https://github.com///commit/.patch and writes
the commit's mail-formatted patch verbatim to .patch in the output
directory, for example patches/b430113.patch. Apply it with git am just
like a per-commit PR patch. --commit cannot be combined with --squash.
Download from GitLab
Pass --gitlab to download a GitLab merge request or commit from gitlab.com.
The project path uses namespace/project form and may contain subgroups, for
example group/subgroup/project:
patchsplit --gitlab zitzhen/patchsplit 1
patchsplit --gitlab zitzhen/patchsplit --commit fafbad69af7507f41e786aa6685b2fa29716c85f
patchsplit --gitlab group/subgroup/project 1 --squash -o mr-1-patches
A merge request is fetched from
https://gitlab.com///-/merge_requests/.patch and,
just like a GitHub PR, split into one mail-formatted patch per commit. With
--squash, the request's .diff net change is written to
mr-.patch. A --commit download fetches
https://gitlab.com///-/commit/.patch and writes the
verbatim patch to .patch. The same --out, --force, and --squash
rules apply, and --commit cannot be combined with --squash.
Download through Tor
Pass --tor to route the download through the Tor network instead of
connecting to GitHub or GitLab directly:
patchsplit zitzhen/patchsplit 16 --tor
patchsplit --gitlab zitzhen/patchsplit 1 --tor -o mr-1-patches
--tor uses the local Tor SOCKS5 endpoint 127.0.0.1:9050, the default for a
Tor daemon. If your Tor client listens elsewhere, for example the Tor Browser
bundle on port 9150, pass the endpoint explicitly:
patchsplit zitzhen/patchsplit 16 --tor=127.0.0.1:9150
patchsplit zitzhen/patchsplit 16 --tor=[::1]:9050
Downloads then run as curl --socks5-hostname , so hostnames are
resolved inside Tor rather than by a local resolver, and redirects are followed
through the same proxy. Any http_proxy, https_proxy, or ALL_PROXY
environment variables are ignored while --tor is active, so an ambient proxy
cannot leak the request. The proxy endpoint must use host:port form; a
malformed value is rejected before downloading.
Start Tor first. If the proxy cannot be reached, patchsplit reports the
endpoint in use and exits with an error instead of falling back to a direct
connection:
error: could not reach the Tor SOCKS5 proxy at 127.0.0.1:9050 (curl exit 7): ...
Options
| Option | Description |
|---|
-o, --out | Output directory (default: patches/). |
-f, --force | Overwrite existing patch files. |
-s, --squash | Write the request's net diff as one patch. |
--gitlab | Download a merge request or commit from gitlab.com. |
--commit | Download one commit's .patch; accepts a short or full hash. |
--tor[=] | Download through the Tor network via a SOCKS5 proxy (default: 127.0.0.1:9050). |
-h, --help | Show help. |
-V, --version | Show version. |
Localization
patchsplit includes Simplified Chinese translations and uses English when
no matching translation is available. Language selection checks these
environment variables in order: PATCHSPLIT_LANGUAGE, LANGUAGE, LC_ALL,
LC_MESSAGES, then LANG.
Override the language for a single command on Linux or macOS:
PATCHSPLIT_LANGUAGE=zh_CN patchsplit --help
PATCHSPLIT_LANGUAGE=C patchsplit --help
Custom UTF-8 .po catalogs can be loaded from PATCHSPLIT_LOCALEDIR or
installation-relative locations next to the executable. External catalogs
take precedence over the built-in catalog for the same locale.
Development
Build a release binary from the repository root:
cargo build --release --locked
The binary is written to target/release/patchsplit (patchsplit.exe on Windows).
Run the test suite with:
cargo test --locked
Tests on Unix also require Git to verify that aggregate patches apply to the
expected file tree.
Updating translations
Refresh the translation template with GNU gettext tools:
scripts/update-pot.sh
The template is generated at po/patchsplit.pot. Source
files used for extraction are listed in po/POTFILES.in.
Contributing
Bug reports, feature requests, documentation improvements, and translations
are welcome. Open an issue with
the command you ran, your OS, the patchsplit version, and the expected and
actual behavior when reporting a bug.
For code changes, add or update tests where relevant and run cargo test --locked
before submitting a pull request. Keep CLI documentation and translations in
sync with user-facing changes.
Release maintenance
Pushing a v* tag triggers GitHub Actions to build release packages for three
platforms and automatically create a GitHub draft release:
git tag v1.x.x
git push origin v1.x.x
You can also run the Release workflow manually from GitHub Actions. Select the
branch or commit to package, then enter the release tag. If the tag does not
exist, it will point to the workflow commit. The workflow creates these files:
patchsplit-linux-x86_64.tar.gz
patchsplit-macos-x86_64.tar.gz
patchsplit-windows-x86_64.zip
patchsplit__amd64.deb
patchsplit--.*.x86_64.rpm
Releases are created as drafts, so they should be reviewed and published from
the GitHub Releases page.
Launchpad PPA
Publishing a GitHub release triggers the Notify PPA workflow. It builds and
signs a Debian source package, then uploads it to Launchpad so the configured
PPA starts building the new version. Configure these repository variables:
PPA_OWNER: Launchpad account name.
PPA_NAME: PPA name, without the ppa: prefix.
PPA_GPG_KEY_ID: full fingerprint of the primary signing key (not a signing subkey ID).
PPA_MAINTAINER_NAME and PPA_MAINTAINER_EMAIL: optional source package metadata.
Store the ASCII-armored private key as the PPA_GPG_PRIVATE_KEY repository
secret. The workflow can also be run manually with a tag, branch, or commit in
the ref input.
License
patchsplit is open source under the MIT License.