patchy-cli
v0.0.27
Published
A CLI tool for managing Git patch workflows.
Readme
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 hereThe 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
patchyOr via npm:
npm install -g patchy-cli
patchyOr use directly without installing:
npx patchy-cli@latestInitialize Patchy
Run this command to initialize Patchy in your project folder:
patchy initpatchy.json reference
{
// Git URL or local file path to clone from.
"source_repo": "https://github.com/example/repo.git", // Override: --source-repo | env: PATCHY_SOURCE_REPO
// Directory containing patch files.
"patches_dir": "./patches/", // Override: --patches-dir | env: PATCHY_PATCHES_DIR
// Default directory for cloning repos.
"clones_dir": "./clones/", // Override: --clones-dir | env: PATCHY_CLONES_DIR
// Path to repo you're generating patches from or applying patches to.
// Can be relative to clones_dir: <clones_dir>/<target_repo> or absolute.
"target_repo": "repo", // Override: --target-repo | env: PATCHY_TARGET_REPO
// Patch set to generate into (subdirectory of patches_dir).
// If not set, prompts interactively or errors in non-interactive mode.
"patch_set": "001-security-fixes", // Override: --patch-set | env: PATCHY_PATCH_SET
// Git SHA or tag to use as the base for patches.
"base_revision": "abc123def", // Override: --base-revision | env: PATCHY_BASE_REVISION
// Remote branch to track for updates (e.g., "main"). Used by `patchy base` to find new commits/tags.
"upstream_branch": "main" // Override: --upstream-branch | env: PATCHY_UPSTREAM_BRANCH
}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.jsonPatches 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 patchesPatch 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
{
"hook_prefix": "my-prefix-" // Override: --hook-prefix | env: PATCHY_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 patchesConflict markers look like standard git merge conflicts:
<<<<<<< current
const value = 999;
=======
const value = 42;
>>>>>>> file.ts.diffEdit 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 tagpatchy prime
Prints a prompt you can include in your AGENTS.md / CLAUDE.md.
Tell your agent to run:
patchy primeOr 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 # falseAvailable 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
