Patchy 🩹
A CLI for generating and applying patches to git repositories.
Why Patchy?
For long-lived git forks with no plans to merge upstream:
- A git fork stores your changes as commits.
- Patches store them as
.diff
Patch files are a clean way to store long-lived changes - human-readable, easy to review and version. But managing them can be cumbersome.
Patchy makes managing patches easy: make changes to a clone → save them as patches → reapply anytime.
How it works
patchy init sets up your project:
├── patchy.json ← config (source: github.com/org/repo, base: v2.1.0)
├── clones/
│ └── upstream-repo/ ← a clone of the source repo - your working copy
└── patches/ ← empty for now - your patches live here
The workflow:
- Make changes directly in
clones/upstream-repo/ - Run
patchy generateto generate patches:
patches/
└── 001-feature-name/
├── src/file.ts.diff ← edits to existing files
└── src/newFile.ts ← new files (no .diff suffix)
- Run
patchy applyto reapply your patches toclones/upstream-repo/anytime
See the example walkthrough for a step-by-step guide.
Getting started
Installation
curl -fsSL https://raw.githubusercontent.com/richardgill/patchy/main/install | bash # follow instructions patchy
Or via npm:
npm install -g patchy-cli patchy
Or use directly without installing:
Initialize Patchy
Run this command to initialize Patchy in your project folder:
patchy.json reference
Precedence: CLI flags > Environment variables > patchy.json
patchy.json uses jsonc, so comments are allowed.
Patch file layout
Patches are stored in the patches/ directory (customizable via patches_dir):
./
├── patches/
│ └── 001-first-patch-set/
│ ├── path/to/existingFile.txt.diff
│ └── path/to/newFile.txt
├── clones/
│ └── repo-clone-1/
│ ├── path/to/existingFile.txt (modified)
│ └── path/to/newFile.txt (added)
└── patchy.json
Patches are grouped into patch sets for organizing related changes. Patch sets have numeric prefixes (e.g., 001-auth, 002-ui) and are applied in order.
Within each patch set files follow the same folder structure as in the source_repo.
Two types of patch files:
.difffiles — For modified existing files (generated viagit diff HEAD)- Plain files — For newly added files (copied verbatim for easier inspection and editing)
patchy generate automatically removes stale files in patches/<patch-set> that no longer correspond to changes in target_repo.
Hooks
Patch sets can include executable scripts that run before and after patches are applied:
patches/
└── 001-add-feature/
├── patchy-pre-apply # runs before patches
├── src/file.ts.diff
├── src/new-file.ts
└── patchy-post-apply # runs after patches
Patch sets support scripts without diffs, enabling pure automation steps.
Hook execution
- Hooks run with
cwdset totarget_repo - Environment variables:
PATCHY_TARGET_REPO,PATCHY_PATCH_SET,PATCHY_PATCHES_DIR,PATCHY_PATCH_SET_DIR,PATCHY_BASE_REVISION - Non-zero exit aborts
patchy apply - Hooks must be executable (
chmod +x)
Custom hook prefix
With prefix my-prefix-, hooks are named my-prefix-pre-apply and my-prefix-post-apply.
Commands
patchy generate
Generate .diff files and new files into ./patches/<patch-set>/ based on current git diff in target_repo.
patchy generate [--patch-set] [--target-repo] [--patches-dir] [--dry-run]
If --patch-set is not provided (and not set via env/config), prompts to select an existing patch set or create a new one.
Note: patchy generate is destructive and will remove any unneeded files in the patch set directory.
patchy apply
Apply patch files from patches/ into target_repo. Patch sets are applied in alphabetical order.
patchy apply [--only <patch-set>] [--until <patch-set>] [--auto-commit=<mode>] [--on-conflict=<mode>] [--target-repo] [--patches-dir] [--dry-run]
| Flag | Description |
|---|---|
--only <name> |
Apply only the specified patch set |
--until <name> |
Apply patch sets up to and including the specified one |
--auto-commit=<mode> |
Control auto-commit behavior (see below) |
--on-conflict=<mode> |
How to handle patches that fail to apply (see below) |
Conflict handling
When a patch doesn't apply cleanly (e.g., the upstream file changed), patchy can insert git-style conflict markers for manual resolution.
| Mode | Behavior |
|---|---|
markers (default) |
Insert conflict markers and continue |
error |
Fail immediately (previous behavior) |
Resolving conflicts:
When conflicts occur, patchy outputs instructions:
✗ Applied patches with conflicts to clones/my-repo
To resolve:
1. Edit files in clones/my-repo to resolve conflicts (remove conflict markers)
2. Run: patchy generate --patch-set 001-fix
3. Commit the updated patches
Conflict markers look like standard git merge conflicts:
<<<<<<< current
const value = 999;
=======
const value = 42;
>>>>>>> file.ts.diff
Edit the file to keep the correct code, remove the marker lines, then regenerate the patch.
Auto-commit behavior
Each patch set creates a single commit with message Apply patch set: <name>. The --auto-commit flag controls when commits happen:
| Mode | Behavior |
|---|---|
interactive (default) |
Commits all patch sets automatically, prompts for the last one |
all |
Commits every patch set immediately after applying |
skip-last |
Commits all except the last patch set |
off |
No commits are made |
Notes:
- In non-interactive environments (e.g., CI),
interactivemode auto-commits everything - Commits are skipped if any patch in the set fails to apply
--dry-runskips all commits
patchy repo reset
Hard reset the Git working tree of target_repo to base_revision. Discards all local changes and patch commits.
patchy repo reset [--base-revision] [--target-repo]
patchy repo clone
Clone a repository into a subdirectory of clones_dir and checkout base_revision. The target directory is derived from the repo name.
patchy repo clone [--source-repo] [--clones-dir] [--base-revision] [--yes]
Use --yes to skip confirmation prompts and automatically update patchy.json with the new target directory.
patchy base [revision]
View or update the base_revision in config.
patchy base # Interactive patchy base abc123def # Set base_revision to the specified SHA or tag
patchy prime
Prints a prompt you can include in your AGENTS.md / CLAUDE.md.
Tell your agent to run:
Or include it directly:
patchy prime >> CLAUDE.mdOutputs a brief description of Patchy, key paths, and essential commands to help AI coding agents understand your project's patch workflow.
patchy config get <key>
Output a single config value (raw, no label). Useful for shell scripts.
patchy config get target_repo_path # /home/user/project/clones/my-repo patchy config get patch_set # 001-feature patchy config get verbose # false
Available keys:
| Key | Description |
|---|---|
source_repo |
Git URL or local file path |
target_repo |
Repository name or path |
clones_dir |
Directory for clones |
patches_dir |
Directory for patches |
patch_set |
Current patch set name |
base_revision |
Base SHA or tag |
upstream_branch |
Remote branch to track |
hook_prefix |
Hook script prefix |
verbose |
Verbose mode ("true"/"false") |
clones_dir_path |
Absolute path to clones directory |
target_repo_path |
Absolute path to target repository |
patches_dir_path |
Absolute path to patches directory |
patch_set_path |
Absolute path to current patch set |
- Unknown keys exit with code 1
- Unset raw keys exit with code 1
- Unset computed keys (e.g.,
patch_set_pathwhenpatch_setis not set) output an empty line
patchy config list
Output all config values as aligned key-value pairs.
patchy config list # source_repo https://github.com/example/repo.git # target_repo my-repo # clones_dir ./clones # patches_dir ./patches # patch_set 001-feature # verbose false # clones_dir_path /home/user/project/clones # target_repo_path /home/user/project/clones/my-repo # ...
Only defined values are shown. Computed path values are resolved to absolute paths.
License
MIT