GitHub - richardgill/patchy: A CLI for generating and applying patches to git repositories

6 min read Original article ↗

Patchy logo

npm package license build status

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:

  1. Make changes directly in clones/upstream-repo/
  2. Run patchy generate to generate patches:
patches/
└── 001-feature-name/
    ├── src/file.ts.diff     ← edits to existing files
    └── src/newFile.ts       ← new files (no .diff suffix)
  1. Run patchy apply to reapply your patches to clones/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:

  • .diff files — For modified existing files (generated via git 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 cwd set to target_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), interactive mode auto-commits everything
  • Commits are skipped if any patch in the set fails to apply
  • --dry-run skips 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.md

Outputs 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_path when patch_set is 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